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

image026.png

Figure 19 – Principle state diagram for SafetyConsumer

Table 32 – 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 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 := 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 Clause 8.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 Clause 8.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 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>

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

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

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 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_calcCRCCheck_i := (CRC_calc == ResponseSPDU.CRC)

// see Clause 8.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

NOTES:

  • 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

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 Clause 8.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 received

<Get ResponseSPDU>

<ResponseSPDU ready for checks>

// 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 == 1Then FaultReqOA_i := 1SAPI.OperatorAckRequested := 0RequestSPDU.Flags.OperatorAckRequested := 0Else // do nothingEndif

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

Endif

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

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 := 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)> 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

S13

// Restart Cycle

[SAPI.Enable==1]

<(Re)Start ConsumerTimer>

T27

S11

S11

// Invalid parameters

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

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