8 Safety communication layer protocol ToC Previous Next

8.1 SafetyProvider and SafetyConsumer ToC Previous Next

8.1.2 OPC UA Safety behavior ToC Previous Next General ToC

The two SCL-services “SafetyProvider” and “SafetyConsumer” are specified using state diagrams. SafetyProvider/-Consumer Sequence diagram ToC

Figure 16 shows the sequence of request and response with SafetyData for OPC UA Safety.

readme_files/image020.png Figure 16 – Sequence diagram for OPC UA Safety (Client/Server)

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

Figure 17 – 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. SafetyProvider state diagram ToC

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

Table 28 – Symbols used for state machines.

Graphical representation Type Description
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, for example receiving an 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.

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.

RequestSPDU_i Variable Local Memory for RequestSPDU (required to react on changes).
SPDU_ID_1_i UInt32 Local variable to store SPDU_ID_1
SPDU_ID_2_i UInt32 Local variable to store SPDU_ID_2
SPDU_ID_3_i UInt32 Local variable to store SPDU_ID_3
BaseID_i GUID Local variable containing the BaseID (taken either from the SPI or SAPI).
ProviderID_i UInt32 Local variable containing the ProviderID (taken either from the SPI or SAPI).
<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
<Calc SPDU_ID_i> Macro    const uint32 SafetyProviderLevel_ID := … // see Clause   If(SAPI.SafetyBaseID == 0) then BaseID_i := SPI.SafetyBaseIDConfigured Else BaseID_i := SAPI.SafetyBaseID   Endif   If(SAPI.SafetyProviderID == 0) then ProviderID_i := SPI.SafetyProviderIDConfigured Else ProviderID_i := SAPI.SafetyProviderID   Endif   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> Macro    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

Initialization    // Initial state   SAPI.SafetyData := 0   SAPI.NonSafetyData := 0 SAPI.MonitoringNumber := 0 SAPI.SafetyConsumerID := 0   SAPI.OperatorAckRequested := 0RequestSPDU_i := 0
S1_WaitForRequest    // waiting on next RequestSPDU from SafetyConsumer <Get RequestSPDU>
S2_PrepareSPDU    ResponseSPDU.Flags.ActivateFSV := SAPI.ActivateFSV ResponseSPDU.Flags.OperatorAckProvider := SAPI.OperatorAckProvider ResponseSPDU.Flags.TestModeActivated := SAPI.EnableTestMode   <Calc SPDU_ID_i>   <build ResponseSPDU> // see Clause
T1 Init S1    
   T2    S1 S2    // RequestSPDU receivedWhen: [RequestSPDU_i<> RequestSPDU]    // Process Request   RequestSPDU_i := RequestSPDU   SAPI.MonitoringNumber := RequestSPDU.MonitoringNumber   SAPI.SafetyConsumerID := RequestSPDU.SafetyConsumerIDSAPI.OperatorAckRequested := RequestSPDU.Flags.OperatorAckRequested
T3 S2 S1    // 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. SafetyConsumer state diagram ToC

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

readme_files/image025.png Figure 19 – Principle state diagram for SafetyConsumer

Table 32 – SafetyConsumer internal items

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.    
FaultReqOA_i Boolean Local memory for errors which request operator acknowledgment.    
OperatorAckConsumerAllowed_i Boolean Auxiliary flag indicating that operator acknowledgment is allowed. It is true, if the input SAPI.OperatorAckConsumer has been ‘false’ since FaultReqOA_i was set.    
MNR_i UInt32 Local Monitoring Number (MNR).    
prevMNR_i UInt32 Local memory for previous MNR    
ConsumerID_i UInt32 Local memory for SafetyConsumerID 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    
   SPI_SafetyConsumerID_i UInt32 Local variable to store the parameter SafetyConsumerID.    
   SPI_SafetyProviderID_i UInt32 Local variable to store the parameter SafetyProviderID.    
   SPI_SafetyBaseID_i UInt128 Local variable to store the parameter SafetyBaseID    
   SPI_SafetyStructureSignature_i UInt32 Local variable to store the parameter SafetyStructureSignature.    
SPI_SafetyOperatorAckNecessary_i Boolean Local variable to store the parameter SafetyOperatorAckNecessary.    
SPI_SafetyErrorIntervalLimit_i UInt16 Local variable to store the parameter SafetyErrorIntervalLimit.    
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.   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.    
ErrorIntervalTimer Timer    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> Macro    // detection of a rising edge:   If   x==true && tmp==false Then result := true Else result := false Endiftmp := x    
<Get ResponseSPDU> Macro Instruction to take the whole ResponseSPDU from the OPC UA Mapper.    
<Use FSV> Macro    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   Endif   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> Macro    SAPI.SafetyData is set to ResponseSPDU.SafetyData   SAPI.NonSafetyData is set to ResponseSPDU.NonSafetyData   SAPI.FSV_Activated := 0    RequestSPDU.Flags.FSV_Activated := 0RequestSPDU.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    If   SAPI.SafetyConsumerID != 0   Then      ConsumerID_i := SAPI.SafetyConsumerID   Else      ConsumerID_i := SPI_SafetyConsumerID_i   Endif   RequestSPDU.SafetyConsumerID := ConsumerID_iRequestSPDU.MonitoringNumber := MNR_i    
<Calc SPDU_ID_i> Macro    uint128 BaseID   uint32 ProviderID   const uint32 SafetyProviderLevel_ID := … // see Clause   If(SAPI.SafetyBaseID == 0)   Then      BaseID := SPI_SafetyBaseID_i Else BaseID := SAPI.SafetyBaseID   Endif   If(SAPI.SafetyProviderID == 0)   Then      ProviderID := SPI_SafetyProviderID_i Else 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_i   SPDU_ID_3_i := BaseID (bytes 8…11) XOR BaseID (bytes 12…15) XOR ProviderID   // see Clause for clarification    
<ParametersOK?> Macro    Boolean result = true If(SAPI.SafetyBaseID == 0 && SPI_SafetyBaseID_i==0) Then result := false Else Endif      If(SAPI.SafetyProviderID == 0 && SPI_SafetyProviderID_i==0) Then result := false Else Endif      If(SAPI.SafetyConsumerID == 0 && SPI_SafetyConsumerID_i==0) Then result := false Else Endif      If(SPI_SafetyStructureSignature_i==0) Then result := false Else Endifyield result    
   <Set Diag(ID,      Boolean isPermanent)> Macro    // ID is the identifier for the type of diagnostic output, see Table 37 // 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 nothing Endif      RequestSPDU.Flags.CommunicationError := isPermanent // NOTE: See Table 37 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 33 – SafetyConsumer states

Initialization    // Initial state of the SafetyConsumer instance. <Use FSV> SAPI.OperatorAckRequested := 0 RequestSPDU.Flags.OperatorAckRequested := 0 SAPI.OperatorAckProvider := 0 FaultReqOA_i := 0 OperatorAckConsumerAllowed_i := 0 SAPI.TestModeActivated := 0 RequestSPDU.Flags.CommunicationError := 0
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.SafetyOperatorAckNecessarySPI_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)
S13_PrepareRequest // Build RequestSPDU and send (done in T16)
S14_WaitForChangedSPDU    // Safety Layer is waiting for next ResponseSPDU from SafetyProvider.// A new ResponseSPDU is characterized by a change in the MNR.
S15_CRCCheckSPDU    // Check CRC      uint32 CRC_calc CRCCheck_i := (CRC_calc == ResponseSPDU.CRC)// see Clause 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 == ConsumerID_i && ResponseSPDU.MNR == MNR_i
S17_Error SAPI.TestModeActivated := 0
S18_ProvideSafetyData // 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

T12 Init S11 -          
T13 S11    S12    //Start[SAPI.Enable==1 && <ParametersOK?>] <(Re)Start ErrorIntervalTimer> <calc SPDU_ID_i> // see Clause for clarification        
T14 S12 S13 //   MNR initialized <(Re)Start ConsumerTimer>        
T15 S18 S11    //   Termination[SAPI.Enable==0]    <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.        
T16 S13 S14    //   Build Request SPDU // and send it    prevMNR_i := MNR_i   If   MNR_i == 0xFFFFFFFFF Then MNR_i := MNR_min Else    MNR_i := MNR_i + 1 Endif   <Build RequestSPDU><Set RequestSPDU>        
T17 S14 S15    // Changed ResponseSPDU // is received<Get ResponseSPDU> [ResponseSPDU.MNR <> prevMNR_i] // A changed ResponseSPDU is characterized by a change in the MNR.        
T18 S14 S17    // WDTimeout[<ConsumerTimer expired?>]    <Set Diag(CommErrTO,isPermanent=true)> <Use FSV>If SPI_SafetyOperatorAckNecessary_i == 1 Then FaultReqOA_i := 1 SAPI.OperatorAckRequested := 0 RequestSPDU.Flags.OperatorAckRequested := 0 Else // do nothing Endif        
T19 S15 S13 //   When CRC err and SafetyErrorIntervalTimer expired   [(crcCheck_i == 0) && <ErrorIntervalTimer expired?>] <(Re)Start ErrorIntervalTimer> <Set Diag(CRCerrIgn, isPermanent=false)>        
T20 S15 S17    //   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 := 1 SAPI.OperatorAckRequested := 0 RequestSPDU.Flags.OperatorAckRequested := 0        
T21 S15 S16    // When CRCCheckOK[crcCheck_i == 1] -        
T22 S16 S18    // SPDU OK[SPDUCheck_i==true]    // For clarification, refer to Figure 20;      // 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   Endif      // Set Flags if OA requested:   If   FaultReqOA_i==1 Then SAPI.OperatorAckRequested := 1, RequestSPDU.Flags.OperatorAckRequested := 1, OperatorAckConsumerAllowed_i := 0, FaultReqOA_i := 0 Else //do nothing Endif    // Wait until OperatorAckConsumer is not active   If   SAPI.OperatorAckConsumer==0   Then          OperatorAckConsumerAllowed_i := 1   Else         //do nothing   Endif      // Reset flags after OA:   If   SAPI.OperatorAckConsumer ==1 && OperatorAckConsumerAllowed_i == 1    Then      SAPI.OperatorAckRequested := 0, RequestSPDU.Flags.OperatorAckRequested := 0 Else      // do nothing   Endif    If SAPI.OperatorAckRequested==1   ResponseSPDU.Flags.ActivateFSV==1 Then <Use FSV> Else <Use PV> 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 == false && <ErrorIntervalTimer expired?>]    <(Re)Start ErrorIntervalTimer>, // Send diagnostic message according the // detected error:   If   ResponseSPDU.SafetyConsumerID <> ConsumerID_i Then <Set Diag(CoIDerrIgn, isPermanent=false)> Else If ResponseSPDU.MNR<>MNR_i Then <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_i Then <Set Diag(SD_IDerrIgn, isPermanent=false)> 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<> ConsumerID_i Then <Set Diag(CoIDerrOA, isPermanent=true)> Else If ResponseSPDU.MNR<>MNR_i Then <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_i Then <Set Diag(SD_IDerrOA, isPermanent=true)> Else //do nothing Endif Endif   FaultReqOA_i := 1SAPI.OperatorAckRequested := 0 RequestSPDU.Flags.OperatorAckRequested := 0 <Use FSV>
T25 S17 S18    // SPDU NOK-          
T26 S18 S13    // Restart Cycle[SAPI.Enable==1] <(Re)Start ConsumerTimer>        
T27 S11 S11    // Invalid parameters[SAPI.Enable==1 && not <ParametersOK?>] <Set Diag(ParametersInvalid, isPermanent=true)> SafetyConsumer sequence diagram for operator acknowledgement (informative) ToC

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

readme_files/image026.png Figure 20 – Sequence diagram for OA

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

Previous Next