[RQ7.14] Figure 16 shows a simplified representation of the state diagram of the SafetyConsumer. The exact behavior is described in Table 33, Table 34, and Table 35. 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 acknowledgment, 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 16 – Principle state diagram for SafetyConsumer
Table 33 – SafetyConsumer 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 |
|
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 |
|
MNR_re_sync_i |
Boolean |
Local variable used to support re-synchronization of MNR after detected error |
|
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
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 26, 6.3.4.3, and 9.4 for more information. NOTE Tthe 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 := trueElse result := falseEndif tmp := 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 := 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. |
|
<Assign ConsumerID>
|
Macro |
If SAPI.SafetyConsumerID != 0 Then ConsumerID_i := SAPI.SafetyConsumerID Else ConsumerID_i := SPI_SafetyConsumerID_i EndifRequestSPDU.SafetyConsumerID := ConsumerID_i
|
|
<Calc SPDU_ID_i> |
Macro |
uint128 BaseID uint32 ProviderID const uint32 SafetyProviderLevel_ID := … // see 7.2.3.3 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 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 7.2.3.2 for clarification |
|
<ParametersOK?> |
Macro |
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)> |
Macro |
// ID is the identifier for the type of diagnostic output, see Table 28// 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 28 for possible values for “ID” and their codes. |
|
<ResponseSPDUready for checks> |
Macro |
Boolean result := false If MNR_re_sync_i == false Then If ResponseSPDU.MNR <> prevMNR_i Then result := true Else //do nothingEndif Else If ResponseSPDU.MNR == MNR_i Then result := true Else //do nothingEndif Endif yield result |
|
<Handle WDTimeout> |
Macro |
<Set Diag(CommErrTO,isPermanent:=true)><Use FSV> If SPI_SafetyOperatorAckNecessary_i == 1Then FaultReqOA_i := 1SAPI.OperatorAckRequested := 0RequestSPDU.Flags.OperatorAckRequested := 0Else // do nothingEndif |
|
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 34 – SafetyConsumer states
STATE NAME |
STATE DESCRIPTION |
Initialization |
// 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 9.2. MNR_i := (previous MNR_i if known) or (random MNR) MNR_i := max(MNR_i, MNR_min)1 |
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_calcCRCCheck_i := (CRC_calc == ResponseSPDU.CRC) // see 7.2.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 == ConsumerID_i && ResponseSPDU.MNR == MNR_i |
S17_Error |
SAPI.TestModeActivated := 0 |
S18_ProvideSafetyData |
// Provide SafetyData to the application program |
S19_SecondWDT_Check |
// Second check of WDTimeout // Prevents restarting of ConsumerTimer if it expired since initial check |
1 This ensures that the MNR is greater or equal to MNR_min, in cases the random number generator yielded a smaller value. |
NOTE 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 safety connection according to this document with the new parameters, and then switch between these two safety connections at runtime.
Table 35 – SafetyConsumer transitions
TRAN-SITION |
SOURCE STATE |
TARGET STATE |
GUARD CONDITION |
ACTIVITY |
T12 |
Init |
S11 |
- |
|
T13 |
S11 |
S12
|
//Start [SAPI.Enable==1 && <ParametersOK?>] |
<(Re)Start ErrorIntervalTimer><calc SPDU_ID_i> // see 7.2.3.2 for clarification |
T14 |
S12 |
S13 |
// MNR initialized |
<(Re)Start ConsumerTimer> <Assign ConsumerID> |
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 == 0xFFFFFFFFFThen MNR_i := MNR_minElse MNR_i := MNR_i + 1Endif RequestSPDU.MonitoringNumber := MNR_i
<Set RequestSPDU> |
T17 |
S14 |
S15 |
// Changed ResponseSPDU// is received1 <Get ResponseSPDU>2 <ResponseSPDU ready for checks> |
// A changed ResponseSPDU is characterized by a change in the MNR. |
T18 |
S14 |
S17 |
// WDTimeout [<ConsumerTimer expired?>] |
<Handle WDTimeout> |
T19 |
S15 |
S13 |
// When CRC err and SafetyErrorIntervalTimer expired[(crcCheck_i == 0) && <ErrorIntervalTimer expired?>] |
MNR_re_sync_i := true <(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 := 1SAPI.OperatorAckRequested := 0RequestSPDU.Flags.OperatorAckRequested := 0 |
T21 |
S15 |
S16 |
// When CRCCheckOK [crcCheck_i == 1] |
- |
T22 |
S16 |
S18 |
// SPDU OK [SPDUCheck_i==true] |
// For clarification, refer to Figure 17;
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 Endif
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 Then OperatorAckConsumerAllowed_i := 1 Else //do nothing Endif
If SAPI.OperatorAckConsumer ==1 && OperatorAckConsumerAllowed_i == 1 Then SAPI.OperatorAckRequested := 0, RequestSPDU.Flags.OperatorAckRequested := 0Else // do nothing Endif 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 |
T23 |
S16 |
S13 |
// 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)>3 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_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> |
T25 |
S17 |
S18 |
// SPDU NOK - |
MNR_re_sync_i := true |
T26 |
S18 |
S19 |
// Restart Cycle [SAPI.Enable==1] |
- |
T27 |
S11 |
S11 |
// Invalid parameters [SAPI.Enable==1 && not <ParametersOK?>] |
<Set Diag(ParametersInvalid, isPermanent:=true)> |
T28 |
S19 |
S13 |
// No WDTimeout |
<(Re)Start ConsumerTimer> |
T29 |
S19 |
S17 |
// WDTimeout [<ConsumerTimer expired?>] |
<Handle WDTimeout> |
1 Another event like “Method completion successful” can be used as guard condition of “Changed ResponseSPDU received” as well. 2 SPDUs with all values (incl. CRC signature) being zero shall be ignored, see requirement RQ5.6. 3 See Table 28. |