7.2.2.5 SafetyConsumer state diagram

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

To avoid unnecessary spurious trips requiring operator acknowledgment, it is recommended that a SafetyConsumer is started after an OPC UA connection to a running SafetyProvider has been established, or that the setting of input SAPI.Enable to “1” is delayed until the SafetyProvider is running.

Figure 18 – Principle state diagram for SafetyConsumer

Table 33 shows the internal items of a SafetyConsumer. A macro is a shorthand representation for operations described in the according definition.

Table 33 – SafetyConsumer internal items
Internal itemsTypeDefinition
Constants
MNR_min:= 0x100UInt32

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

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

Variables
FaultReqOA_iBooleanLocal memory for errors which request operator acknowledgment
OperatorAckConsumerAllowed_iBooleanAuxiliary flag indicating that operator acknowledgment is allowed. It is true if the input SAPI.OperatorAckConsumer has been ‘false’ since FaultReqOA_i was set
MNR_iUInt32Local MonitoringNumber (MNR)
prevMNR_iUInt32Local memory for previous MNR
ConsumerID_iUInt32Local memory for SafetyConsumerID in use
CRCCheck_iBooleanLocal variable used to store the result of the CRC check
SPDUCheck_iBooleanLocal variable used to store the result of the additional SPDU checks
SPDU_ID_1_iUInt32Local variable to store the expected SPDU_ID_1
SPDU_ID_2_iUInt32Local variable to store the expected SPDU_ID_2
SPDU_ID_3_iUInt32Local variable to store the expected SPDU_ID_3
SPI_SafetyConsumerID_iUInt32Local variable to store the parameter SafetyConsumerID
SPI_SafetyProviderID_iUInt32Local variable to store the parameter SafetyProviderID
SPI_SafetyBaseID_iUInt128Local variable to store the parameter SafetyBaseID
SPI_SafetyStructureSignature_iUInt32Local variable to store the parameter SafetyStructureSignature
SPI_SafetyOperatorAckNecessary_iBooleanLocal variable to store the parameter SafetyOperatorAckNecessary
SPI_SafetyErrorIntervalLimit_iUInt16Local variable to store the parameter SafetyErrorIntervalLimit
MNR_re_sync_iBooleanLocal variable used to support re-synchronization of MNR after detected error
Timers
ConsumerTimerTimer

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, see RQ7.26.

ErrorIntervalTimerTimer

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 The local variable SPI_SafetyErrorIntervalLimit_i is not to 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
Endif

tmp:= x

<Get ResponseSPDU>MacroInstruction 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 1 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 2 The NonSafetyData is always updated if data is available. In case of a timeout, no data is available, which is indicated using binary zero. If it is necessary for an application to distinguish between “no data available” and “binary zero received”, it can add a Boolean variable to the NonSafetyData. This value is set to ”1” during normal operation, and to ”0” 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>MacroInstruction to transfer the whole RequestSPDU to the OPC UA Mapper.
<(Re)Start ConsumerTimer>MacroRestarts the ConsumerTimer.
<(Re)Start ErrorIntervalTimer>MacroRestarts the ErrorIntervalTimer.
<ConsumerTimer expired?>MacroYields “true” if the timer is running longer than SPI.SafetyConsumerTimeout since last restart, “false” otherwise.
<ErrorIntervalTimer expired?>MacroYields “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

Endif
RequestSPDU.SafetyConsumerID:= ConsumerID_i

<Calc SPDU_ID_i>Macro

uint128 BaseID

uint32 ProviderID

const uint32 SafetyProviderLevel_ID:= … // see 7.2.3.4

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 (octets 0…3)
XOR SafetyProviderLevel_ID

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

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

// see 7.2.3.2 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
Endif

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 nothing
Endif

RequestSPDU.Flags.CommunicationError:= isPermanent
// NOTE See Table 28 for possible values for “ID” and their codes.

<ResponseSPDU
ready for checks>
Macro

Boolean result:= false

If MNR_re_sync_i == false

Then
If ResponseSPDU.MNR <> prevMNR_i

Then result:= true

Else
//do nothing
Endif

Else

If ResponseSPDU.MNR == MNR_i

Then result:= true

Else
//do nothing
Endif

Endif

yield result

<Handle WDTimeout>Macro

<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

External event
Restart cycleEventThe external call of SafetyConsumer can be interpreted as event “restart cycle”

Table 34 shows the states of the SafetyConsumer. The SafetyConsumer 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 becomes effective immediately (see RQ7.26). If this is not the desired behaviour, 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 switching between these two safety connections at runtime.

Table 34 – SafetyConsumer states
State nameState description
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

MNR_re_sync_i:= false

S11_Wait for (Re)Start

// Safety layer is waiting for (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_calc
CRCCheck_i:= (CRC_calc == ResponseSPDU.CRC)

// see 7.2.3.6 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_ErrorSAPI.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.

Table 35 shows the transitions of the SafetyConsumer.

Table 35 – SafetyConsumer transitions
Tran
sition
Source stateTarget stateGuard conditionActivity
T12InitS11-
T13S11S12

//Start

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

<(Re)Start ErrorIntervalTimer>
<calc SPDU_ID_i>
// see 7.2.3.2 for clarification
T14S12S13// MNR initialized

<(Re)Start ConsumerTimer>

<Assign ConsumerID>

T15S18S11

// 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 could

// be necessary to stop the ConsumerTimer here.

T16S13S14// Build RequestSPDU
// and send it

prevMNR_i:= MNR_i

If MNR_i == 0xFFFFFFFFF
Then
MNR_i:= MNR_min
Else

MNR_i:= MNR_i + 1
Endif

RequestSPDU.MonitoringNumber:= MNR_i

<Set RequestSPDU>

T17S14S15

// Changed ResponseSPDU
// is received1

<Get ResponseSPDU>2

<ResponseSPDU ready for checks>

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

// WDTimeout

[<ConsumerTimer expired?>]

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

MNR_re_sync_i:= true

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

T20S15S17

// When CRC err and ErrorIntervalTimer 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

T21S15S16

// When CRCCheckOK

[crcCheck_i == 1]

-
T22S16S18

// SPDU OK

[SPDUCheck_i==true]

// For clarification, refer to Figure 19;

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

// 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

T23S16S13// SPDU NOK and ErrorIntervalTimer expired

[SPDUCheck_i == false && <ErrorIntervalTimer expired?>]

<(Re)Start ErrorIntervalTimer>,

MNR_re_sync_i:= true

// Send diagnostic message according to 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)>3
Else
// do nothing
Endif
Endif

T24S16S17

// SPDU NOK and ErrorIntervalTimer not expired


[SPDUCheck_i == 0 && not <ErrorIntervalTimer expired?>]

<(Re)Start ErrorIntervalTimer>
// Send diagnostic message according to 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:= 1

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

T25S17S18

// SPDU NOK

-

MNR_re_sync_i:= true
T26S18S19

// Restart Cycle

[SAPI.Enable==1]

-
T27S11S11

// Invalid parameters

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

<Set Diag(ParametersInvalid, isPermanent:=true)>
T28S19S13// No WDTimeout<(Re)Start ConsumerTimer>
T29S19S17

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