This chapter describes in detail the protocol that is used for the safety communication layer.

Figure 14 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.

NOTE: The RequestSPDU does not contain a CRC-checksum.


Figure 14 – RequestSPDU

Figure 15 shows the structure of a ResponseSPDU which originates at the SafetyProvider and contains the safety data (1 – 1500 Bytes), an additional 25 Byte safety code (STrailer) as described in the subsequent clauses, and the non-safety related data (see also Clause 6.2.4 for details).


Figure 15 – ResponseSPDU

NOTE: 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 element which stores data. A different MNR is used in every RequestSPDU of a given SafetyConsumer, and a ResponseSPDU will only be accepted if its MNR is matching the MNR of the corresponding RequestSPDU.

The checking for correctness of the MNR is only performed by the SafetyConsumer.

[RQ8.1] The flags of the Safety Consumer (RequestSPDU.Flags) shall be used as shown in Clause 6.2.1.

[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 Clause 6.2.2.

[RQ8.4] Flags in the ResponseSPDU.Flags which are 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

[RQ8.5] The SafetyConsumerID in the ResponseSPDU shall be a copy of the SafetyConsumerID received in the corresponding RequestSPDU. See Clause

[RQ8.6] The MonitoringNumber in the ResponseSPDU shall be a copy of the MonitoringNumber received in the corresponding RequestSPDU. See Clause

NOTE: The SafetyConsumer uses the ResponseSPDU.MonitoringNumber to detect mis-timed SPDUs, e.g., such SPDUs which are continuously repeated by an erroneous network element which stores data. A different MonitoringNumber is used in every RequestSPDU of a given SafetyConsumer, and a ResponseSPDU will only be accepted if its MonitoringNumber matches the MonitoringNumber in the corresponding RequestSPDU.

[RQ8.7] This CRC-checksum shall be used to detect data corruption. See Clause 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 16 and Figure 17 show sequences of requests and responses with SafetyData for OPC UA Safety using Client/Server and PubSub communication mechanisms, respectively. Please note that the figures show selected scenarios only and are therefore informative.


Figure 16 (informative) – Sequence diagram for OPC UA Safety (Client/Server)

[RQ8.23] In the case of Client/Server communication, a SafetyConsumer’s OPC UA mapper may call a SafetyProvider (either the state machine implementation itself or the SafetyProvider’s OPC UA mapper) with an identical RequestSPDU multiple times in a row. In that case, the SafetyProvider (state machine or OPC UA mapper) shall answer all requests.

NOTE: The returned ResponseSPDUs may contain different values (e.g., currently available process values) or contain the initially returned values.

[RQ8.24] For each SafetyProvider, the implemented choice of behavior according to RQ8.23 (i.e., whether currently available process values or initially returned values will be used) shall be documented in the corresponding safety manual.

NOTE: Since a SafetyConsumer checks for changed MNRs in ResponseSPDUs (see SafetyConsumer state S14_WaitForChangedSPDU in Table 33 and transition T17 in Table 34), and therefore will use only the first evaluated ResponseSPDU for a given MNR. All further ResponseSPDUs with the same MNR will be ignored.


NOTE: The bold arrows represent communication with new data values, whereas dashed arrows contain repeated data values.

Figure 17 (informative) – Sequence diagram for OPC UA Safety (PubSub)

NOTE: the OPC UA state machines do not contain any retry-mechanisms to increase fault tolerance. In contrast, it is assumed that retry is already handled within the OPC UA stack (e.g., when using Client/Server, or by choosing a higher update rate for OPC UA PubSub). The dashed lines therefore are not part of this document, but rather symbolize the repeated sending of data implemented in the OPC UA stack.

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 in Table 34). 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 execution of the SafetyConsumer to the next execution of the SafetyConsumer. The implementation and error reaction of ConsumerCycleTime is not part of this document; it is vendor-specific.

[RQ8.10] Figure 18 shows a simplified representation of the state diagram of the SafetyProvider. The exact behavior is described in Table 29, Table 30, and Table 31. The SafetyProvider shall implement that behavior. It is not required to literally follow the entries given in the tables, if the externally observable behavior does not change.


Figure 18 – Simplified representation of the state diagram for the SafetyProvider

Table 28 – Symbols used for state machines.

Graphical representation




Activity State

Within these interruptible activity states the SafetyProvider waits for new input.


Action State

Within these non-interruptible action states events like new requests are deferred until the next activity state is reached, see [1].

The transitions are fired in case of an event. “Event” in this context means either a method call in the case of Client/Server communication or the detection of a changed RequestSPDU by the OPC UA mapper in the case of PubSub communication.

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.

NOTE: The details on how to implement activity states and action states are vendor-specific. Typically, in a real-time system the task performing the SafetyProvider or SafetyConsumer state machine is executed cyclically (see 5.2). Whenever the task is woken up by the scheduler of the operating system while it is in an action state, it executes action states until its time slice is used up, or an activity state is reached. Whenever a task being in an activity state is woken up, it checks for input. If no new input is available, it immediately returns to the sleep state without changing state.

If input is available, it starts executing action states until its time-slice is up or until the next activity state is reached.

Table 29 – SafetyProvider instance internal items






Local Memory for RequestSPDU (required to react on changes).



Local variable to store SPDU_ID_1



Local variable to store SPDU_ID_2



Local variable to store SPDU_ID_3



Local variable containing the BaseID (taken either from the SPI or SAPI).



Local variable containing the ProviderID (taken either from the SPI or SAPI).

<Get RequestSPDU>


Instruction to take the whole RequestSPDU from the OPC UA Mapper.

<Set ResponseSPDU>


Instruction to transfer the whole ResponseSPDU to the OPC UA Mapper

<Calc SPDU_ID_i>


const uint32 SafetyProviderLevel_ID := … // see Clause

If(SAPI.SafetyBaseID == 0) thenBaseID_i := SPI.SafetyBaseIDConfiguredElse BaseID_i := SAPI.SafetyBaseID

Endif If(SAPI.SafetyProviderID == 0) thenProviderID_i := SPI.SafetyProviderIDConfiguredElse ProviderID_i := SAPI.SafetyProviderID


SPDU_ID_1_i := BaseID_i (bytes 0…3) XOR SafetyProviderLevel_ID

SPDU_ID_2_i := BaseID_i (bytes 4…7) XOR SPI.SafetyStructureSignature

SPDU_ID_3_i := BaseID_i (bytes 8…11) XOR BaseID_i (bytes 12…15) XOR ProviderID_i

// see Clause for clarification

<build ResponseSPDU>


Take the MNR and the SafetyConsumerID of the received RequestSPDU. Add the SPDU_ID_1_i, SPDU_ID_2_i, SPDU_ID_3_i, Flags, the SafetyData and the NonSafetyData, as well as the calculated CRC.

See Clause

Table 30 – States of SafetyProvider instance




// Initial state

SAPI.SafetyData := 0

SAPI.NonSafetyData := 0SAPI.MonitoringNumber := 0SAPI.SafetyConsumerID := 0

SAPI.OperatorAckRequested := 0

RequestSPDU_i := 0


// waiting on next RequestSPDU from SafetyConsumer

<Get RequestSPDU>


ResponseSPDU.Flags.ActivateFSV := SAPI.ActivateFSVResponseSPDU.Flags.OperatorAckProvider := SAPI.OperatorAckProvider ResponseSPDU.Flags.TestModeActivated := SAPI.EnableTestMode

<Calc SPDU_ID_i>

<build ResponseSPDU> // see Clause

Table 31 – SafetyProvider transitions












// RequestSPDU received


// Process Request

RequestSPDU_i := RequestSPDU

SAPI.MonitoringNumber := RequestSPDU.MonitoringNumber

SAPI.SafetyConsumerID := RequestSPDU.SafetyConsumerID

SAPI.OperatorAckRequested := RequestSPDU.Flags.OperatorAckRequested




// SPDU is prepared


<Set ResponseSPDU>

NOTE: the SafetyProvider does not check for correct configuration. It will reply to requests even if it is incorrectly configured (e.g., its SafetyProviderID is zero). However, SafetyConsumers will never try to communicate with SafetyProviders having incorrect parameters, see Transitions T13/T27 in Table 34 and the macro <ParametersOK?> in Table 32.

[RQ8.11] Figure 19 shows a simplified representation of the state diagram of the SafetyConsumer. The exact behavior is described in Table 32, Table 33, and Table 34. The SafetyConsumer shall implement this behavior. It is not required to literally follow the entries given in the tables, if the externally observable behavior does not change.

NOTE: in order to avoid unnecessary spurious trips requiring operator acknowledgement, the SafetyConsumers should only be started after an OPC UA connection to a running SafetyProvider has been established, or setting the input SAPI.Enable should be delayed until the SafetyProvider is running.


Figure 19 – Principle state diagram for SafetyConsumer

Table 32 – SafetyConsumer internal items





MNR_min := 0x100


// 0x100 is the start value for MNR, also used after wrap-around

// The values 0…0xFF are reserved for future use




Local memory for errors which request operator acknowledgment



Auxiliary flag indicating that operator acknowledgment is allowed. It is true if the input SAPI.OperatorAckConsumer has been ‘false’ since FaultReqOA_i was set



Local Monitoring Number (MNR)



Local memory for previous MNR



Local memory for SafetyConsumerID in use



Local variable used to store the result of the CRC-check



Local variable used to store the result of the additional SPDU-checks



Local variable to store the expected SPDU_ID_1



Local variable to store the expected SPDU_ID_2



Local variable to store the expected SPDU_ID_3



Local variable to store the parameter SafetyConsumerID



Local variable to store the parameter SafetyProviderID



Local variable to store the parameter SafetyBaseID



Local variable to store the parameter SafetyStructureSignature



Local variable to store the parameter SafetyOperatorAckNecessary



Local variable to store the parameter SafetyErrorIntervalLimit



Local variable used to support re-synchronization of

MNR after detected error




This timer is used to check whether the next valid ResponseSPDU has arrived on time. It is initialized using the parameter SPI.SafetyConsumerTimeOut

NOTE: As opposed to other parameters, a modification of the parameter value SafetyConsumerTimeout takes effect immediately, i.e., not only when state S11 is visited.



This timer is used to check the elapsed time between errors. If the elapsed time between two consecutive errors is smaller than the value SafetyErrorIntervalLimit, FSV will be activated. Otherwise, the ResponseSPDU is discarded and the SafetyConsumer waits for the next ResponseSPDU.

This timer is initialized using the local variable SPI_SafetyErrorIntervalLimit_i.

See Table 27, Clause 7.4.2, and Clause 11.4 for more information.

NOTE: the local variable SPI_SafetyErrorIntervalLimit_i should not be confused with the parameter SPI.SafetyErrorIntervalLimit. The local variable is copied from the parameter in state S11 (restart). Hence, if the parameter value changes during runtime, the new value will only be used after the connection has been restarted.

Macros <...><...>

<risingEdge x>


// detection of a rising edge:

If x==true && tmp==false Then result := trueElse result := falseEndif

tmp := x

<Get ResponseSPDU>


Instruction to take the whole ResponseSPDU from the OPC UA Mapper.

<Use FSV>


SAPI.SafetyData is set to binary 0

If [<ConsumerTimer expired || SAPI.Enable==0 ?>]Then SAPI.NonSafetyData is set to binary 0

Else SAPI.NonSafetyData is set to ResponseSPDU.NonSafetyData


SAPI.FSV_Activated := 1

RequestSPDU.Flags.FSV_Activated := 1

NOTE: If a safety application prefers fail-safe values other than binary 0, this can be implemented in the safety application by querying SAPI.FSV_Activated.

NOTE: the non-safety data is always updated if data is available. In case of a timeout, no data is available, which is indicated using binary zero. If an application needs to distinguish between “no data available” and “binary zero received”, it can add a Boolean variable to the NonSafetyData. This value is set to ‘one’ during normal operation, and to ‘zero’ for indicating that no data is available.

<Use PV>


SAPI.SafetyData is set to ResponseSPDU. SafetyData

SAPI.NonSafetyData is set to ResponseSPDU.NonSafetyData

SAPI.FSV_Activated := 0

RequestSPDU.Flags.FSV_Activated := 0

RequestSPDU.Flags.CommunicationError := 0

<Set RequestSPDU>


Instruction to transfer the whole RequestSPDU to the OPC UA Mapper

<(Re)Start ConsumerTimer>


Restarts the consumer timer.

<(Re)Start ErrorIntervalTimer>


Restarts the error interval timer.

<ConsumerTimer expired?>


Yields “true” if the timer is running longer than SPI.SafetyConsumerTimeOut since last restart, “false” otherwise.

<ErrorIntervalTimer expired?>


Yields “true” if the timer is running longer than SPI.SafetyErrorIntervalLimit since last restart, “false” otherwise.

<Assign ConsumerID>


If SAPI.SafetyConsumerID != 0


ConsumerID_i := SAPI.SafetyConsumerID


ConsumerID_i := SPI_SafetyConsumerID_i

EndifRequestSPDU.SafetyConsumerID := ConsumerID_i

<Calc SPDU_ID_i>


uint128 BaseID

uint32 ProviderID

const uint32 SafetyProviderLevel_ID := … // see Clause

If(SAPI.SafetyBaseID == 0)

Then BaseID := SPI_SafetyBaseID_iElse BaseID := SAPI.SafetyBaseID

Endif If(SAPI.SafetyProviderID == 0)

Then ProviderID := SPI_SafetyProviderID_iElse ProviderID := SAPI.SafetyProviderID


SPDU_ID_1_i := BaseID (bytes 0…3) XOR SafetyProviderLevel_ID

SPDU_ID_2_i := BaseID (bytes 4…7) XOR SPI_SafetyStructureSignature_i

SPDU_ID_3_i := BaseID (bytes 8…11) XOR BaseID (bytes 12…15) XOR ProviderID

// see Clause for clarification



Boolean result := trueIf(SAPI.SafetyBaseID == 0 && SPI_SafetyBaseID_i==0)Thenresult := falseElseEndif

If(SAPI.SafetyProviderID == 0 && SPI_SafetyProviderID_i==0)Thenresult := falseElseEndif

If(SAPI.SafetyConsumerID == 0 && SPI_SafetyConsumerID_i==0)Thenresult := falseElseEndif

If(SPI_SafetyStructureSignature_i==0)Thenresult := falseElseEndif

yield result

<Set Diag(ID,

Boolean isPermanent)>


// ID is the identifier for the type of diagnostic output, see Table 37// Parameter "isPermanent" 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 := isPermanent

// NOTE: See Table 37 for possible values for “ID” and their codes.

<ResponseSPDUready for checks>


Boolean result := false

If MNR_re_sync_i == false

Then If ResponseSPDU.MNR <> prevMNR_i

Then result := true

Else //do nothingEndif


If ResponseSPDU.MNR == MNR_i

Then result := true

Else //do nothingEndif


yield result

External Event

Restart Cycle


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 33 – SafetyConsumer states




// Initial state of the SafetyConsumer instance.

<Use FSV> SAPI.OperatorAckRequested := 0RequestSPDU.Flags.OperatorAckRequested := 0SAPI.OperatorAckProvider := 0FaultReqOA_i := 0OperatorAckConsumerAllowed_i := 0SAPI.TestModeActivated := 0RequestSPDU.Flags.CommunicationError := 0

MNR_re_sync_i:= false

S11_Wait for (Re)Start

// Safety Layer is waiting (Re)Start

// Changes to these parameters are only considered in this state

// Exception: a change of SafetyConsumerTimeout is possible during operation

// Read parameters from the SPI and store them in local variables:

SPI_SafetyConsumerID_i := SPI.SafetyConsumerID

SPI_SafetyProviderID_i := SPI.SafetyProviderIDConfigured

SPI_SafetyBaseID_i := SPI.SafetyBaseIDConfigured

SPI_SafetyStructureSignature_i := SPI.SafetyStructureSignature

SPI_SafetyOperatorAckNecessary_i := SPI.SafetyOperatorAckNecessary

SPI_SafetyErrorIntervalLimit_i := SPI_SafetyErrorIntervalLimit

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)


// Build RequestSPDU and send (done in T16)


// Safety Layer is waiting for next ResponseSPDU from SafetyProvider.

// A new ResponseSPDU is characterized by a change in the MNR.


// Check CRC

uint32 CRC_calcCRCCheck_i := (CRC_calc == ResponseSPDU.CRC)

// see Clause on how to calculate CRC_calc


// 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 == ConsumerID_i && ResponseSPDU.MNR == MNR_i


SAPI.TestModeActivated := 0


// Provide SafetyData to the application program


  • The consumer parameters are accessed only in state S11. In this state, a copy is made, and in all other states and transitions, the copied values are used. This ensures that a change of one of these parameters takes effect only when a new safety connection is established.
  • The only exception from this rule is the parameter SafetyConsumerTimeout. A change of this parameter may become effective immediately.
  • If this is not the desired behavior, i.e., if parameters should be changeable during runtime, this can be accomplished by establishing a second OPC UA Safety connection with the new parameters, and then switch between these connections at runtime.

Table 34 – SafetyConsumer transitions














[SAPI.Enable==1 && <ParametersOK?>]

<(Re)Start ErrorIntervalTimer><calc SPDU_ID_i> // see Clause for clarification




// MNR initialized

<(Re)Start ConsumerTimer>

<Assign ConsumerID>




// Termination


<Use FSV>

RequestSPDU.Flags.CommunicationError := 0

// necessary to make sure that no diagnostic

// message is lost, see macro <Set Diag ...>

// NOTE: depending on its implementation, it might

// be necessary to stop the ConsumerTimer here.




// Build Request SPDU // and send it

prevMNR_i := MNR_i

If MNR_i == 0xFFFFFFFFFThen MNR_i := MNR_minElse

MNR_i := MNR_i + 1Endif

RequestSPDU.MonitoringNumber := MNR_i

<Set RequestSPDU>




// Changed ResponseSPDU// is received

<Get ResponseSPDU>

<ResponseSPDU ready for checks>

// A changed ResponseSPDU is characterized by a change in the MNR.




// WDTimeout

[<ConsumerTimer expired?>]

<Set Diag(CommErrTO,isPermanent:=true)><Use FSV>

If SPI_SafetyOperatorAckNecessary_i == 1Then FaultReqOA_i := 1SAPI.OperatorAckRequested := 0RequestSPDU.Flags.OperatorAckRequested := 0Else // do nothingEndif




// When CRC err and SafetyErrorIntervalTimer expired[(crcCheck_i == 0) && <ErrorIntervalTimer expired?>]

MNR_re_sync_i := true

<(Re)Start ErrorIntervalTimer><Set Diag(CRCerrIgn, isPermanent:=false)>




// When CRC err and SafetyErrorIntervalTimer not expired

[(crcCheck_i == 0) && not <ErrorIntervalTimer expired?>]

<(Re)Start ErrorIntervalTimer><Set Diag(CRCerrOA, isPermanent:=true)><Use FSV>

FaultReqOA_i := 1SAPI.OperatorAckRequested := 0RequestSPDU.Flags.OperatorAckRequested := 0




// When CRCCheckOK

[crcCheck_i == 1]







// For clarification, refer to Figure 20;

MNR_re_sync_i := false

// indicate OA from provider

SAPI.OperatorAckProvider := ResponseSPDU.Flags.OperatorAckProvider

// OA requested due to rising edge at ActivateFSV?

If (<risingEdge ResponseSPDU.Flags.ActivateFSV>&& SPI_SafetyOperatorAckNecessary_i == true)Then FaultReqOA_i := 1;<Set Diag(FSV_Requested,isPermanent:=true)> Else // do nothing


// Set Flags if OA requested:

If FaultReqOA_i==1 Then SAPI.OperatorAckRequested := 1, RequestSPDU.Flags.OperatorAckRequested := 1, OperatorAckConsumerAllowed_i := 0,FaultReqOA_i := 0Else //do nothingEndif

// Wait until OperatorAckConsumer is not active

If SAPI.OperatorAckConsumer==0


OperatorAckConsumerAllowed_i := 1


//do nothing


// Reset flags after OA:

If SAPI.OperatorAckConsumer ==1 && OperatorAckConsumerAllowed_i == 1


SAPI.OperatorAckRequested := 0, RequestSPDU.Flags.OperatorAckRequested := 0Else

// do nothing


If SAPI.OperatorAckRequested==1 || ResponseSPDU.Flags.ActivateFSV==1Then <Use FSV> Else <Use PV>Endif

// Notify safety application that SafetyProvider is in test mode:

SAPI.TestModeActivated := ResponseSPDU.Flags.TestModeActivated




// SPDU NOK and SafetyErrorIntervalTimer expired[SPDUCheck_i == false && <ErrorIntervalTimer expired?>]

<(Re)Start ErrorIntervalTimer>,

MNR_re_sync_i := true

// Send diagnostic message according the// detected error:

If ResponseSPDU.SafetyConsumerID <> ConsumerID_iThen <Set Diag(CoIDerrIgn, isPermanent:=false)>ElseIf ResponseSPDU.MNR<>MNR_iThen <Set Diag(MNRerrIgn, isPermanent:=false)> 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, isPermanent:=false)> Else // do nothing Endif Endif




// 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<> ConsumerID_iThen <Set Diag(CoIDerrOA, isPermanent:=true)>Else If ResponseSPDU.MNR<>MNR_iThen <Set Diag(MNRerrOA, isPermanent:=true)> 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_IDerrOA, isPermanent:=true)> Else //do nothing Endif Endif

FaultReqOA_i := 1

SAPI.OperatorAckRequested := 0RequestSPDU.Flags.OperatorAckRequested := 0<Use FSV>






MNR_re_sync_i := true




// Restart Cycle


<(Re)Start ConsumerTimer>




// Invalid parameters

[SAPI.Enable==1 && not <ParametersOK?>]

<Set Diag(ParametersInvalid, isPermanent:=true)>

Figure 20 shows the sequence after the detection of an error requiring operator acknowledge until communication returns to delivering process values again.


Figure 20 – Sequence diagram for OA

After the error is gone the sequence follows the logic of T22 in Table 34.

[RQ8.12] The ResponseSPDU shall be built by the SafetyProvider by copying RequestSPDU.MonitoringNumber and RequestSPDU.SafetyConsumerID into the ResponseSPDU. After this, SPDU_ID, Flags, the SafetyData and the NonSafetyData shall be updated. Finally, ResponseSPDU.CRC shall be calculated and appended.


Figure 21 – Overview of task for SafetyProvider

For the ResponseSPDU.Flags, see Clause For the calculation of the SPDU_ID, see Clause For the calculation of CRC, see Clause

[RQ8.13] SPDU_ID_1, SPDU_ID_2 and SPDU_ID_3 shall be calculated according to

Figure 22 and Table 35.


Figure 22 – Calculation of the SPDU_ID

Table 35 – Presentation of the SPDU_ID

SPDU_ID_1 := SafetyBaseID (bytes 0…3) XOR SafetyProviderLevel_ID (bytes 0…3)

SPDU_ID_2 := SafetyBaseID (bytes 4…7) XOR SafetyStructureSignature (bytes 0…3)

SPDU_ID_3 := SafetyBaseID (bytes 8…11) XOR SafetyBaseID (bytes 12…15) XOR SafetyProviderID (bytes 0…3)

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 probably 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_ID1 differs, but SPDU_ID2 and SPDU_ID3 do not, the SafetyProviderLevel does not match.

By 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.

The SafetyProviderLevel is the SIL the SafetyProvider implementation (hardware & software) is capable of.

Table 36 – Coding for the SafetyProviderLevel_ID


Value of SafetyProviderLevel_ID



[RQ8.14] Exactly one of the values provided in Table 36 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 3.2.

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, the identifier of the structure type (SafetyStructureIdentifier) is also taken into account when calculating SafetyStructureSignature. This ensures that the SafetyProvider and the 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] SafetyStructureSignature shall be calculated as CRC32-signature (polynomial: 0x F4ACFB13, see Annex B.1) over SafetyStructureIdentifier (encoding: UTF-8), SafetyStructureSignatureVersion and the sequence of the DataType IDs. After each datatype ID, a 16-bit zero-value (0x0000) shall be inserted. All integers shall be encoded using little endian byte ordering. Data shall be processed in reverse order, see Annex B.1. The value “0” shall not be used as signature. Instead, the value “1” shall be used in this case.

The terminating zero of SafetyStructureIdentifier shall not be considered when calculating the CRC.

[RQ8.17] SafetyStructureIdentifier may be visible in the OPC UA information model for diagnostic purposes but shall not be evaluated by the SafetyConsumer during runtime.

[RQ8.18] For all releases up to Release 2.0 of the specification, the value for SafetyStructureSignatureVersion shall be 0x0001.


SafetyStructureIdentifier,e.g. “Motörhead” = 0x4d 0x6f 0x74 0xc3 0xb6 0x72 0x68 0x65 0x61 0x64

SafetyStructureSignatureVersion := 0x0001

1. DataType Int16: (Id = 0x0004), // see Clause 6.4

2. DataType Boolean: (Id = 0x0001),

3. DataType Float32: (Id =0x000A)

SafetyStructureSignature =

= CRC32_Backward(0x4d, 0x6f, 0x74, 0xc3, 0xb6, 0x72, 0x68, 0x65, 0x61, 0x64,


0x04,0x00, 0x00,0x00,

0x01,0x00, 0x00,0x00,

0x0A, 0x00, 0x00, 0x00) =

= CRC32_Forward(

0x00, 0x00, 0x00, 0x0A,0x00, 0x00, 0x00, 0x01,

0x00, 0x00, 0x00, 0x04,


= 0xe2e86173

NOTE: The insertion of 0x0000 values after the DataType ID, allows for introducing arrays in later version of OPC UA Safety.

NOTE: SafetyStructureSignatureVersion is the version of the procedure used to calculate the signature, as defined in [RQ8.16]. If future releases of this specification define an alternative procedure, they will indicate this by using a different version number.

OPC 10000-3, clause 5.8.2 defines different categories of DataTypes. Regarding the DataType ID which is to be used within the StructureSignature, the following holds:

  • For Built-in DataTypes, the ID from Table 1 of OPC 10000-6 is used as DataType ID.
  • For Simple DataTypes, the ID of the Built-in DataType from which they are derived is used.
  • As of now, Structured DataTypes (including OptionSets) shall not be used within Safety Data. Arrays are not supported. Instead, multiple variables of the same type are used.
  • Enumeration DataTypes are encoded on the wire as Int32 and therefore shall use the ID of the Int32 Built-in DataType.

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., if it is of data type UInt16, Int16 or Float32), it shall be decoded and encoded using little endian order in which the least significant byte appears first in the incremental memory address stream.

[RQ8.21] The calculation sequence shall begin with the highest memory address (n) of the STrailer counting back to the lowest memory address (0) and then include also the SafetyData beginning with the highest memory address.

Figure 23 and shows the calculation sequence of a CRC_SPDU on a little-endian machine, using an example SafetyData with the following fields:

Int32 var1



Int16 var4


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, from bottom to top. In the example shown in Figure 23, CRC calculation starts at byte index 33 (most significant byte of the MNR) and ends at byte index 0.

NOTE: the reverse order ensures that the effectiveness of the CRC mechanism remains independent of any CRCs used within the underlying OPC UA channel, even if it would coincidentally use the same CRC polynomial.


Figure 23 – Calculation of the CRC (on little-endian machines, CRC32_Backward)

An alternative way to calculate the CRC (particularly useful on big-endian machines) is shown in Figure 24. Here, the individual elements of the ResponseSPDU are already arranged in memory in reversed order, and CRC calculation is executed from byte 0 to byte 33.


Figure 24 – Calculation of the CRC (on big-endian machines, CRC32_Forward)

[RQ8.22] On the SafetyConsumer, CRC_calc shall be calculated using data received in the ResponseSPDU, and not from expected values.