DUTs

E_DeviceState

{attribute 'qualified_only'}
TYPE E_DeviceState :
(
    INVALID := 0,
    INIT := EC_DEVICE_STATE_INIT,
    PREOP := EC_DEVICE_STATE_PREOP,
    BOOTSTRAP := EC_DEVICE_STATE_BOOTSTRAP,
    SAFEOP := EC_DEVICE_STATE_SAFEOP,
    OP := EC_DEVICE_STATE_OP
) WORD := INVALID;
END_TYPE
Related:

ST_DeviceDiagnostic

// PLC Terminal device diagnostics
TYPE ST_DeviceDiagnostic :
STRUCT
    {attribute 'pytmc' := '
        pv: TYPE
        io: i
    '}
    sType : STRING[15];
    {attribute 'pytmc' := '
        pv: ADDRESS
        io: i
    '}
    nAddr : WORD;
    {attribute 'pytmc' := '
        pv: NAME
        io: i
    '}
    sName : STRING[31];
    {attribute 'pytmc' := '
        pv: DEVICE_STATE
        io: i
        field: ZRVL 0
        field: ONVL 1
        field: TWVL 2
        field: THVL 3
        field: FRVL 4
        field: SXVL 8
        field: ZRST INVALID
        field: ONST INIT
        field: TWST PREOP
        field: THST BOOTSTRAP
        field: FRST SAFEOP
        field: SXST OP
        field: ZRSV MAJOR
        field: ONSV MAJOR
        field: TWSV MAJOR
        field: THSV MAJOR
        field: FRSV MAJOR
        field: FVSV MINOR
        field: SXSV NO_ALARM
        field: UNSV MAJOR
    '}
    nDeviceState : ENUM;
    {attribute 'pytmc' := '
        pv: DEVICE_STATE_FLAGS
        io: i
    '}
    nDeviceStateFlags : BYTE;
    {attribute 'pytmc' := '
        pv: LINK_STATE_FLAGS
        io: i
    '}
    nLinkStateFlags : BYTE;
END_STRUCT
END_TYPE

ST_ErrorStates

// Error states that can occur in the program
TYPE ST_ErrorStates :
STRUCT
    // Leak Alarms
    bLeakDetected : BOOL; // Leak detected on one of the sensors

    // Hardware Failure
    bHardwareFailure : BOOL; // A hardware component has failed

END_STRUCT
END_TYPE

GVLs

GVL_IO

// PLC IO from terminal modules
{attribute 'qualified_only'}
VAR_GLOBAL
    (* Slot1 EL2212 -- Cooling Valve Outputs *)
    {attribute 'pytmc' := '
                pv: @(PREFIX):VALVE:01
                io: i
                field: ZNAM "CLOSED"
                field: ONAM "OPEN"
    '}
    {attribute 'TcLinkTo' :=    'TIIB[FTL_PLC_E02_EL2212]^DOX Control Channel 1^Control^Output'}
    bChillerValve01 AT %Q* : BOOL; // Ch1
    {attribute 'pytmc' := '
                pv: @(PREFIX):VALVE:02
                io: i
                field: ZNAM "CLOSED"
                field: ONAM "OPEN"
    '}
    {attribute 'TcLinkTo' :=    'TIIB[FTL_PLC_E02_EL2212]^DOX Control Channel 2^Control^Output'}
    bChillerValve02 AT %Q* : BOOL; // Ch2

    (* Slot2 EL1004 -- Leak Detector Inputs *)
    {attribute 'pytmc' := '
                pv: @(PREFIX):DETECTOR:01
                io: i
                field: ZNAM "OK"
                field: ONAM "LEAK"
    '}
    {attribute 'TcLinkTo' :=    'TIIB[FTL_PLC_E03_EL1004]^Channel 1^Input'}
    bLeakRelay01 AT %I* : BOOL; // Ch1
    {attribute 'pytmc' := '
                pv: @(PREFIX):DETECTOR:02
                io: i
                field: ZNAM "OK"
                field: ONAM "LEAK"
    '}
    {attribute 'TcLinkTo' :=    'TIIB[FTL_PLC_E03_EL1004]^Channel 2^Input'}
    bLeakRelay02 AT %I* : BOOL; // Ch2

END_VAR

GVL_PLC

// PLC global variables
{attribute 'qualified_only'}
VAR_GLOBAL
    AMSNetId AT %I* : AMSNETID;
END_VAR

POUs

F_ChillerValveEnable

// Interlock logic for the Dump Chiller
FUNCTION F_ChillerValveEnable : BOOL
VAR_INPUT
END_VAR
VAR_IN_OUT CONSTANT
    stErrors : ST_ErrorStates; // Currently detected errors
END_VAR
VAR
END_VAR
IF stErrors.bHardwareFailure OR stErrors.bLeakDetected THEN
    F_ChillerValveEnable := FALSE;
ELSE
    F_ChillerValveEnable := TRUE;
END_IF

END_FUNCTION
Related:

FB_EcatDiag

(*
Ecat bus diagnostic tool
2015-11-4 Alex Wallace
This function block checks the states of all slaves on the ecat bus network,
it could be modified to export the states of the slaves on an individual basis,
but for now it sets the output boolean true if all slaves are OP and false otherwise.
To start the block provide a falling edge on the first pass boolean input.

2018-05-05 Margaret Ghaly
Function block has been modified to retrieve the Device State of the Ethercat Master.
It also exports the states and information of each individual configured Slave.
And saves them in the array q_aEcConfSlaveInfo.

2019-08-28 Nathan Telles
Customized for EPS project using PyTMC. Export all states to a structure which is
linked to EPICS records. Remove unecessary diagnostics.
*)
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_EcatDiag
VAR_INPUT
    {attribute 'naming' := 'omit'}
    AMSNetId : AMSNETID; //Link to the AMSNETID name in the ethercat master info.
    //i_xFirstPass: BOOL; //Hook to system first pass boolean for proper intialization (must be true for the first cycle of the PLC)
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bError : BOOL;
    {attribute 'pytmc' := '
        pv: SLAVES_OK
        io: i
        field: OSV NO_ALARM
        field: ZSV MAJOR
        field: ZNAM ERROR
        field: ONAM OK
    '}
    bAllSlaveStatesGood: BOOL; // Set to True if all Slaves are in OP State
    {attribute 'pytmc' := '
        pv: MASTER_OK
        io: i
        field: OSV NO_ALARM
        field: ZSV MAJOR
        field: ZNAM ERROR
        field: ONAM OK
    '}
    bMasterStateGood : BOOL;
END_VAR
VAR
    {attribute 'pytmc' := '
        pv: MASTER_DEV_STATE
        io: i
    '}
    nMasterDevState: WORD; // The Device State of the Master
    {attribute 'pytmc' := '
        pv: MASTER_STATE
        io: i
    '}
    nMasterState: WORD; // The State of the Master
    {attribute 'pytmc' := '
        pv: AMS_ID
        io: i
    '}
    sNetId: STRING(23); //NetId string

    astTermStates: ARRAY[1..MAX_SLAVES] OF ST_EcSlaveState; //ECAT Slave States Buffer
    astEcConfSlaveInfo: ARRAY[1..MAX_SLAVES] OF ST_EcSlaveConfigData; //ECAT Slave Configs Buffer

    fbGetAllSlaveStates: FB_EcGetAllSlaveStates; //Acquires the ECAT Slave States puts them into astTermStates
    fbGetMasterDevState: FB_EcGetMasterDevState; //Acquires ECAT Master State
    fbGetMasterState: FB_EcGetMasterState; //Acquires ECAT Master State
    fbGetConfSlaves: FB_EcGetConfSlaves; //Acquires the ECAT slave configuration of the bus (how many, what kind, etc)

    {attribute 'naming' := 'omit'}
    ftReset: F_TRIG; //Reset trigger sensor
    {attribute 'naming' := 'omit'}
    ftMasterDevReset: F_TRIG; //Retrigger sensor for GetMasterDevState
    {attribute 'naming' := 'omit'}
    ftMasterReset: F_TRIG; //Retrigger sensor for GetMasterState

    {attribute 'pytmc' := 'pv: SLAVE01'}
    stSlave01 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE02'}
    stSlave02 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE03'}
    stSlave03 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE04'}
    stSlave04 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE05'}
    stSlave05 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE06'}
    stSlave06 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE07'}
    stSlave07 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE08'}
    stSlave08 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE09'}
    stSlave09 : ST_DeviceDiagnostic;
    {attribute 'pytmc' := 'pv: SLAVE10'}
    stSlave10 : ST_DeviceDiagnostic;

    aSlaves : ARRAY [1..MAX_SLAVES] OF POINTER TO ST_DeviceDiagnostic := [ADR(stSlave01),ADR(stSlave02),
        ADR(stSlave03),ADR(stSlave04),ADR(stSlave05),ADR(stSlave06),ADR(stSlave07),ADR(stSlave08),
        ADR(stSlave09), ADR(stSlave10)];

    nIterator: UINT;
    bInit : BOOL := TRUE;

    {attribute 'pytmc' := '
        pv: SLAVES_COUNT
        io: i
    '}
    nSlaves : UINT;
END_VAR
VAR CONSTANT
    MAX_SLAVES : UINT := 10;
END_VAR
sNetId := F_CreateAmsNetId(AMSNetId);

//Query the state of all terminals, collect in astTermStates
ftReset(CLK:=fbGetAllSlaveStates.bBusy OR bInit);
fbGetAllSlaveStates.bExecute := ftReset.Q;
fbGetAllSlaveStates(sNetId:=sNetId, pStateBuf := ADR(astTermStates), cbBufLen:=SIZEOF(astTermStates));
//Keep checking...


//Cycle through each entry in the array and check if we have anyone not in OP and that the link state is good.
// If so, then set our global IO bad boolean.
IF fbGetAllSlaveStates.nSlaves > 0 THEN
    bAllSlaveStatesGood := TRUE;
    FOR nIterator := 1 TO MAX_SLAVES DO
        IF nIterator > fbGetAllSlaveStates.nSlaves // Slave is missing
        OR NOT( (astTermStates[nIterator].deviceState = EC_DEVICE_STATE_OP) AND (astTermStates[nIterator].linkState = EC_LINK_STATE_OK))
        THEN
            bAllSlaveStatesGood := FALSE;
        END_IF

        IF nIterator <= fbGetAllSlaveStates.nSlaves THEN
            aSlaves[nIterator]^.nDeviceState := astTermStates[nIterator].deviceState AND EC_DEVICE_STATE_MASK;
            aSlaves[nIterator]^.nDeviceStateFlags := SHR(astTermStates[nIterator].deviceState, 4);
            aSlaves[nIterator]^.nLinkStateFlags :=astTermStates[nIterator].linkState;
        ELSE
            // Missing slaves should be set to invalid
            aSlaves[nIterator]^.nDeviceState := E_DeviceState.INVALID;
        END_IF
    END_FOR
END_IF

// Read the EtherCAT state of the master. If the call is successful,
//the State output variable of type WORD contains the requested status information.
ftMasterDevReset(CLK:=fbGetMasterDevState.bBusy OR bInit);
fbGetMasterDevState(sNetId:= sNetId, bExecute:=ftMasterDevReset.Q, nDevState => nMasterDevState);

ftMasterReset(CLK:=fbGetMasterState.bBusy OR bInit);
fbGetMasterState(sNetId:= sNetId, bExecute:=ftMasterDevReset.Q, state => nMasterState);

bMasterStateGood:= (fbGetMasterState.state = BYTE_TO_UINT(EC_DEVICE_STATE_OP) AND fbGetMasterDevState.nDevState = 0);

//This function is used to read a list of all configured slaves from the EtherCat master object Directory
//needs to run only once
fbGetConfSlaves(bExecute := bInit, sNetId :=sNetId, pArrEcConfSlaveInfo := ADR(astEcConfSlaveInfo),cbBufLen := SIZEOF(astEcConfSlaveInfo));
nSlaves:=fbGetConfSlaves.nSlaves;

IF  NOT (fbGetConfSlaves.bBusy) THEN
    FOR nIterator := 1 TO MIN(MAX_SLAVES, fbGetConfSlaves.nSlaves) DO
        IF nIterator > fbGetConfSlaves.nSlaves THEN // Missing terminal
            aSlaves[nIterator]^.nAddr := 0;
            aSlaves[nIterator]^.sName := '';
            aSlaves[nIterator]^.sType := '';
        ELSE
            aSlaves[nIterator]^.nAddr :=astEcConfSlaveInfo[nIterator].nAddr;
            aSlaves[nIterator]^.sName :=astEcConfSlaveInfo[nIterator].sName;
            aSlaves[nIterator]^.sType :=astEcConfSlaveInfo[nIterator].sType;
        END_IF
    END_FOR
    fbGetConfSlaves.bExecute := FALSE;
END_IF


bInit := FALSE;
bError := fbGetMasterDevState.bError OR fbGetMasterState.bError OR fbGetAllSlaveStates.bError OR
    fbGetConfSlaves.bError OR NOT bAllSlaveStatesGood OR NOT bMasterStateGood;

END_FUNCTION_BLOCK
Related:

FB_ErrorTriggers

// Detects rising-edge errors
FUNCTION_BLOCK FB_ErrorTriggers
VAR_IN_OUT CONSTANT
    stErrors : ST_ErrorStates; // Errors that have been detected
END_VAR
VAR_OUTPUT
    stNewErrors : ST_ErrorStates; // Rising-edge errors
END_VAR
VAR
    // R_TRIG function blocks for detecting new errors
    fbLeakTrig : R_TRIG;
END_VAR
fbLeakTrig(
    CLK := stErrors.bLeakDetected,
    Q => stNewErrors.bLeakDetected
);

END_FUNCTION_BLOCK

// Returns whether a rising-edge leak error has occured
METHOD NewLeakError : BOOL
VAR_INPUT
END_VAR
NewLeakError := fbLeakTrig.Q;
END_METHOD

// Reset latched leak errors
METHOD ResetLeakTriggers
VAR_INPUT
END_VAR
fbLeakTrig(
    CLK := FALSE,
    Q => stNewErrors.bLeakDetected
);
END_METHOD
Related:

FB_LeakMonitor

// Detect leaks and leak locations
FUNCTION_BLOCK FB_LeakMonitor
VAR_INPUT
    bLeakRelay01 : BOOL; // Whether a leak was detected on sensor 1
    bLeakRelay02 : BOOL; // Whether a leak was detected on sensor 2
END_VAR
VAR_OUTPUT

    {attribute 'pytmc' := '
            pv: LEAK_FOUND
            io: i
            field: OSV MAJOR
            field: ZSV NO_ALARM
            field: ZNAM OK
            field: ONAM LEAK
    '}
    bError: BOOL := FALSE; //Error flag
END_VAR
(* Set error flag if leak is detected on either sensor*)
IF (bLeakRelay01 = FALSE OR bLeakRelay02 = FALSE) THEN
    bError := TRUE;
END_IF

END_FUNCTION_BLOCK

// Reset latched errors
METHOD Reset
VAR_INPUT
END_VAR
bError := FALSE;
END_METHOD

MAIN

PROGRAM MAIN
VAR
    stErrors : ST_ErrorStates; // Triggered error states
    fbErrorTriggers : FB_ErrorTriggers; // Brand new errors this cycl

    {attribute 'pytmc' := 'pv: @(PREFIX):LEAK_MONITOR'}
    fbLeakMonitor : FB_LeakMonitor; //Leak detection
    {attribute 'pytmc' := '
            pv: @(PREFIX):VALVE:RESET
            io: o
    '}
    bValveLatchReset : BOOL; //Manual latch for valves

    (*
    {attribute 'pytmc' := 'pv: @(PREFIX):FTL:PLC'}
    fbDiag : FB_EcatDiag; // Ethercat diagnostic
    *)
END_VAR
(*
(* Check PLC hardware for failures *)
fbDiag(AMSNetId := GVL_PLC.AMSNetId);
stErrors.bHardwareFailure := fbDiag.bError;
*)

(* Monitor for leaks on cooling loop*)
fbLeakMonitor(
    bLeakRelay01 := GVL_IO.bLeakRelay01,
    bLeakRelay02 := GVL_IO.bLeakRelay02,
    bError => stErrors.bLeakDetected
);

(* Open NC chiller loop valves if interlock logic passes *)
GVL_IO.bChillerValve01 := F_ChillerValveEnable(stErrors := stErrors);
GVL_IO.bChillerValve02 := F_ChillerValveEnable(stErrors := stErrors);

(* Check for rising-edge errors *)
fbErrorTriggers(stErrors := stErrors);

(* Check for new leak errors *)
IF fbErrorTriggers.NewLeakError() THEN
    bValveLatchReset := FALSE; //If there is a new error turn off the valve reset
ELSIF bValveLatchReset THEN
    // If there are no new errors and reset is enabled, reset the errors
    fbLeakMonitor.Reset();
    fbErrorTriggers.ResetLeakTriggers();
END_IF

END_PROGRAM
Related: