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:

E_LeakLocation

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_LeakLocation :
(
    NO_LEAK := 0,
    LOC1 := 1,
    LOC2 := 2,
    LOC3 := 4,
    LOC4 := 8,
    LOC5 := 16,
    LOC6 := 32
) BYTE;
END_TYPE

E_Mode

{attribute 'qualified_only'}
TYPE E_Mode :
(
    MASTER_Override     :=1,
    Protection          := 2,
    AMPHOS_Maintenance  := 3
);
END_TYPE

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
    bLeakOpticalTableAmphosOn   : BOOL; // Leak on the optical table with Amphos on
    bLeakOpticalTableNoLaser    : BOOL; // Leak on the optical table with no laser on
    bLeakOpcpaCarbideOn         : BOOL; // Leak inside the OPCPA with Carbide on
    bLeakUnderTable             : BOOL; // Leak under the table
    bLeakInsideRacks            : BOOL; // Leak inside the racks

    // Temperature loop alarms
    bChillerLoop01              : BOOL; // Temperature too high in loop 01
    bChillerLoop02              : BOOL; // Temperature too high in loop 02
    bChillerLoop03              : BOOL; // Temperature too high in loop 03
    bChillerLoop04              : BOOL; // Temperature too high in loop 04
    bChillerLoop05              : BOOL; // Temperature too high in loop 05

    // Beam Errors
    bAmphosBeam                 : BOOL; // Amphos beam error
    bOpcpaBeam                  : BOOL; // OPCPA beam error
    bMpcBeam                    : BOOL; // MPC beam error

    (*
    // Temperature monitors
    bTempMon01 : BOOL; // Temperature too high at location 01
    bTempMon02 : BOOL; // Temperature too high at location 02
    bTempMon03 : BOOL; // Temperature too high at location 03
    bTempMon04 : BOOL; // Temperature too high at location 04
    bTempMon05 : BOOL; // Temperature too high at location 05
    bTempMon06 : BOOL; // Temperature too high at location 06
    bTempMon07 : BOOL; // Temperature too high at location 07
    bTempMon08 : BOOL; // Temperature too high at location 08
    *)

    // Dump chiller
    bDumpChiller                : BOOL; // Dump chiller error
    bBaseplateChiller           : BOOL; // Baseplate chiller error

    (*
    // Amphos MRC
    bAmphosMRC01 : BOOL; // Amphos MRC 01 Error
    bAmphosMRC02 : BOOL; // Amphos MRC 01 Error
    bAmphosMRC03 : BOOL; // Amphos MRC 01 Error
    *)

    // Hardware Failure
    bHardwareFailure            : BOOL; // A component of the EPS has failed

END_STRUCT
END_TYPE

ST_LaserSetpoints

// Setpoints for the lasers
TYPE ST_LaserSetpoints :
STRUCT
    {attribute 'pytmc' := '
        pv: MIN_VOLTAGE
        io: io
        field: EGU Volts
    '}
    nMinVoltage : LREAL; // Minimum voltage the laser will be allowed to operate at
    {attribute 'pytmc' := '
        pv: MIN_NOMINAL_VOLTAGE
        io: io
        field: EGU Volts
    '}
    nMinNominalVoltage : LREAL; // Minimum voltage for the laser to be operating nominally
END_STRUCT
END_TYPE

ST_LeakBoundaries

// Boundaries indicating leak monitor locations
TYPE ST_LeakBoundaries :
STRUCT
    {attribute 'pytmc' := '
        pv: L_MIN01
        io: io
    '}
    nLMin1 : UINT;
    {attribute 'pytmc' := '
        pv: L_MAX01
        io: io
    '}
    nLMax1 : UINT;
    {attribute 'pytmc' := '
        pv: L_MIN02
        io: io
    '}
    nLMin2 : UINT;
    {attribute 'pytmc' := '
        pv: L_MAX02
        io: io
    '}
    nLMax2 : UINT;
    {attribute 'pytmc' := '
        pv: L_MIN03
        io: io
    '}
    nLMin3 : UINT;
    {attribute 'pytmc' := '
        pv: L_MAX03
        io: io
    '}
    nLMax3 : UINT;
    {attribute 'pytmc' := '
        pv: L_MIN04
        io: io
    '}
    nLMin4 : UINT;
    {attribute 'pytmc' := '
        pv: L_MAX04
        io: io
    '}
    nLMax4 : UINT;
    {attribute 'pytmc' := '
        pv: L_MIN05
        io: io
    '}
    nLMin5 : UINT;
    {attribute 'pytmc' := '
        pv: L_MAX05
        io: io
    '}
    nLMax5 : UINT;
    {attribute 'pytmc' := '
        pv: L_MIN06
        io: io
    '}
    nLMin6 : UINT;
    {attribute 'pytmc' := '
        pv: L_MAX06
        io: io
    '}
    nLMax6 : UINT;
END_STRUCT
END_TYPE

ST_TemperatureMonitorSetpoints

// Setpoints for the temperature monitors
TYPE ST_TemperatureMonitorSetpoints :
STRUCT
    {attribute 'pytmc' := '
        pv: MAX_TEMP
        io: io
        field: EGU C
    '}
    fMaxTemperature : LREAL; // Maximum temperature in Celsius allowed before triggering error
END_STRUCT
END_TYPE

GVLs

Global_Version

{attribute 'TcGenerated'}
// This function has been automatically generated from the project information.
VAR_GLOBAL CONSTANT
    {attribute 'const_non_replaced'}
    {attribute 'linkalways'}
    stLibVersion_EPS : ST_LibVersion := (iMajor := 0, iMinor := 0, iBuild := 0, iRevision := 0, sVersion := '0.0.0');
END_VAR

GVL_EL3174

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    (* Analog scaling *)
    fRawUpperLimit : LREAL := 16#7736; // Highest raw signal that can be read
    fRawLowerLimit : LREAL := 16#0000; // Lowest raw signal that can be read
    fScaleUpperLimit : LREAL := 10; // Upper limit in volts
    fScaleLowerLimit : LREAL := 0; // Lower limit in volts
END_VAR

GVL_IO

// PLC IO from terminal modules
{attribute 'qualified_only'}
VAR_GLOBAL
    (* CX2020 *)

    (* Slot1 EL3174 *)
    {attribute 'TcLinkTo' :=    'TIIB[LPS_PLC_E01_EL3174]^AI Standard Channel 1^Value'}
    nOpcpaVoltageRaw AT %I* : INT; // Ch1

    {attribute 'TcLinkTo' :=    'TIIB[LPS_PLC_E01_EL3174]^AI Standard Channel 2^Value'}
    nAmphosVoltageRaw AT %I* : INT; // Ch2

    {attribute 'TcLinkTo' :=    'TIIB[LPS_PLC_E01_EL3174]^AI Standard Channel 3^Value'}
    nMpcVoltageRaw AT %I* : INT; // Ch3


    (* Slot2 EL3174 *)


    (* Slot3 EL9189 *)

    (* Slot4 EL9184 *)

    (*
    (* Slot5 EL3174 *)
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E05_EL3174]^AI Standard Channel 1^Value'}
    nTempMonRaw01 AT %I* : INT; // Ch1
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E05_EL3174]^AI Standard Channel 2^Value'}
    nTempMonRaw02 AT %I* : INT; // Ch2
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E05_EL3174]^AI Standard Channel 3^Value'}
    nTempMonRaw03 AT %I* : INT; // Ch3
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E05_EL3174]^AI Standard Channel 4^Value'}
    nTempMonRaw04 AT %I* : INT; // Ch4

    (* Slot6 EL3174 *)
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E06_EL3174]^AI Standard Channel 1^Value'}
    nTempMonRaw05 AT %I* : INT; // Ch1
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E06_EL3174]^AI Standard Channel 2^Value'}
    nTempMonRaw06 AT %I* : INT; // Ch2
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E06_EL3174]^AI Standard Channel 3^Value'}
    nTempMonRaw07 AT %I* : INT; // Ch3
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E06_EL3174]^AI Standard Channel 4^Value'}
    nTempMonRaw08 AT %I* : INT; // Ch4
    *)

    (* Slot7 EL1259 Inputs *)
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:AMPHOS:POWER_STATE
        io: i
        field: ZNAM "OFF"
        field: ONAM "ON"
    '}
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTI Inputs 10x Channel 1^Status^Input state'}
    bAmphosOn AT %I* : BOOL; // Ch1
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTI Inputs 10x Channel 2^Status^Input state'}
    bLeakDetected AT %I* : BOOL; // Ch2
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTI Inputs 10x Channel 3^Status^Input state'}
    bLoopTempSW01 AT %I* : BOOL; // Ch3 Loop 01 NC Temp switch
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTI Inputs 10x Channel 4^Status^Input state'}
    bLoopTempSW02 AT %I* : BOOL; // Ch4 Loop 02 NC Temp switch
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTI Inputs 10x Channel 5^Status^Input state'}
    bLoopTempSW03 AT %I* : BOOL; // Ch5 Loop 03 NC Temp switch
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTI Inputs 10x Channel 6^Status^Input state'}
    bLoopTempSW04 AT %I* : BOOL; // Ch6 Loop 04 NC Temp switch

    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTI Inputs 10x Channel 7^Status^Input state'}
    bBaseplateChillerFlow AT %I* : BOOL; // Ch7 Baseplate chiller NC flow sensor


    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTI Inputs 10x Channel 8^Status^Input state'}
    bDumpChillerFlow AT %I* : BOOL; // Ch2 Dump chiller NC flow sensor


    (* Slot7 EL1259 Outputs *)
    (*
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:AMPHOS_ATTENUATOR:ENABLE
        io: i
        field: OSV NO_ALARM
        field: ZSV MAJOR
        field: ZNAM DISABLED
        field: ONAM ENABLED
    '}
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E06]^Channel 1^Output'}
    bAmphosAttenuatorEnable AT %Q* : BOOL; // Ch1


    bAmphosAttenuatorEnableManualOperation AT %Q* : BOOL := TRUE;
    *)

    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:AMPHOS:SHUTTER_ENABLE
        io: i
        field: OSV NO_ALARM
        field: ZSV MAJOR
        field: ZNAM DISABLED
        field: ONAM ENABLED
    '}
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E07_EL1259]^MTO Outputs 10x Channel 1^Ctrl^Manual output state'}
    bAmphosShutterEnable AT %Q* : BOOL; // Ch1


    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:AMPHOS:SHUTTER_CMD
        io: io
        field: ZNAM "OFF"
        field: ONAM "ON"
    '}
    bAmphosShutterManualOverride : BOOL ; // CMD to Amphos for Shutter Operation

    (* Slot8 EL9184 *)

    (* Slot9 EL1259 *)
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E09_EL1259]^MTI Inputs 10x Channel 2^Status^Input state'}
    bLoopTempSW05 AT %I* : BOOL; // Ch2 Loop 05 NC Temp switch (labelled TEMP SW04 on CAPTAR)

    (* Slot10 EL9184 *)

    (* Slot11 ES2624 *)
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:DUMP_CHILLER_RELAY:ENABLE
        io: i
        field: OSV NO_ALARM
        field: ZSV MAJOR
        field: ZNAM DISABLED
        field: ONAM ENABLED
    '}
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E11_EL2624]^Channel 1^Output'}
    bDumpChillerRelay AT %Q* : BOOL; // Ch1 Dump chiller NC relay

    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:BASEPLATE_CHILLER_RELAY:ENABLE
        io: i
        field: OSV NO_ALARM
        field: ZSV MAJOR
        field: ZNAM DISABLED
        field: ONAM ENABLED
    '}
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E11_EL2624]^Channel 2^Output'}
    bBaseplateChillerRelay AT %Q* : BOOL; // Ch2 Baseplate chiller NC relay

    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:AMPHOS:ENABLE
        io: i
        field: OSV NO_ALARM
        field: ZSV MAJOR
        field: ZNAM DISABLED
        field: ONAM ENABLED
    '}
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E11_EL2624]^Channel 3^Output'}
    bAmphosRelay AT %Q* : BOOL; // Ch3 Amphos Interlock relay

    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:CARBIDE_SHUTTER:ENABLE
        io: i
        field: OSV NO_ALARM
        field: ZSV MAJOR
        field: ZNAM DISABLED
        field: ONAM ENABLED
    '}
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E11_EL2624]^Channel 4^Output'}
    bCarbideShutterEnable AT %Q* : BOOL; // Ch4 HASS


    bCarbideShutterEnableManualOperation AT %Q* : BOOL := TRUE;

    (* Slot12 EL6022 *)

    (* Slot13 EL9505 *)

    (* Slot14 EL1124 *)
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:MPC:POWER_STATE
        io: i
        field: ZNAM "OFF"
        field: ONAM "ON"
    '}
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E14_EL1124]^Channel 1^Input'}
    bCarbideOn AT %I* : BOOL; // Ch1


    (*
    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E14_EL1124]^Channel 2^Input'}
    bAmphosMRC01 AT %I* : BOOL; // Ch2 NC Amphos MRC switch

    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E14_EL1124]^Channel 3^Input'}
    bAmphosMRC02 AT %I* : BOOL; // Ch3 NC Amphos MRC switch

    {attribute 'TcLinkTo' := 'TIIB[LPS_PLC_E14_EL1124]^Channel 4^Input'}
    bAmphosMRC03 AT %I* : BOOL; // Ch4 NC Amphos MRC switch
    *)

END_VAR

GVL_Laser

// Setpoints for lasers
{attribute 'qualified_only'}
VAR_GLOBAL PERSISTENT
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:AMPHOS'}
    stAmphosSP  : ST_LaserSetpoints; // Amphos setpoints
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:OPCPA'}
    stOpcpaSP   : ST_LaserSetpoints; // OPCPA setpoints
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:MPC'}
    stMpcSP   : ST_LaserSetpoints; // MPC setpoints
END_VAR
Related:

GVL_PLC

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

GVL_TemperatureMonitor

// Temperature monitor setpoints
{attribute 'qualified_only'}
VAR_GLOBAL PERSISTENT
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON01'}
    stTempMonSP01 : ST_TemperatureMonitorSetpoints;
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON02'}
    stTempMonSP02 : ST_TemperatureMonitorSetpoints;
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON03'}
    stTempMonSP03 : ST_TemperatureMonitorSetpoints;
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON04'}
    stTempMonSP04 : ST_TemperatureMonitorSetpoints;
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON05'}
    stTempMonSP05 : ST_TemperatureMonitorSetpoints;
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON06'}
    stTempMonSP06 : ST_TemperatureMonitorSetpoints;
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON07'}
    stTempMonSP07 : ST_TemperatureMonitorSetpoints;
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON08'}
    stTempMonSP08 : ST_TemperatureMonitorSetpoints;
END_VAR
Related:

GVL_TraceTek

// Global variables for the TraceTek leak detector
{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    (* Modbus address *)
    nModbusSlaveAddress : BYTE := 199; // Modbus slave address of the TraceTek device
END_VAR
VAR_GLOBAL PERSISTENT
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:LEAK'}
    stLeakMonitorBoundaries : ST_LeakBoundaries; // Boundaries of possible leak locations
END_VAR
Related:

POUs

F_BaseplateChillerEnable

// Interlock logic for the Baseplate Chiller
FUNCTION F_BaseplateChillerEnable : BOOL
VAR_INPUT
    bMasterOverride : BOOL; //Master manual override control input
END_VAR
VAR_IN_OUT CONSTANT
    stErrors : ST_ErrorStates; // Currently detected errors
END_VAR
VAR
END_VAR
IF (stErrors.bHardwareFailure // Disable baseplate chiller if there is a hardware failure
    (* OR stErrors.bLeakUnderTable *) OR stErrors.bLeakOpcpaCarbideOn) // Disable baseplate chiller if there is a leak under the table
    AND NOT bMasterOverride //if master override is TRUE, ignore all errors
THEN
    F_BaseplateChillerEnable := FALSE;
ELSE
    F_BaseplateChillerEnable := TRUE;
END_IF

END_FUNCTION
Related:

F_DumpChillerEnable

// Interlock logic for the Dump Chiller
FUNCTION F_DumpChillerEnable : BOOL
VAR_INPUT
    bMasterOverride : BOOL; //Master manual override control input
END_VAR
VAR_IN_OUT CONSTANT
    stErrors : ST_ErrorStates; // Currently detected errors
END_VAR
VAR
END_VAR
IF (stErrors.bHardwareFailure // Disable dump chiller if there's a hardware failure
    OR stErrors.bLeakOpticalTableAmphosOn OR stErrors.bLeakOpticalTableNoLaser (* OR stErrors.bLeakUnderTable *) ) // Disable dump chiller for these leak conditions
    AND NOT bMasterOverride //if master override is TRUE, ignore all errors
THEN
    F_DumpChillerEnable := FALSE;
ELSE
    F_DumpChillerEnable := TRUE;
END_IF

END_FUNCTION
Related:

FB_BaseplateChiller

// Monitors the baseplate chiller from the flow sensor
FUNCTION_BLOCK FB_BaseplateChiller
VAR_INPUT
    bAmphosOn : BOOL; // Whether the amphos is turned on
    bBaseplateChillerFlow : BOOL; // Signal from the flow sensor. Normally high.
    bMasterLatchReset : BOOL; //Master latched error reset
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; // Whether an error was detected. Should latch until reset
END_VAR
VAR
END_VAR
(* Latch error if the baseplate chiller flow detects an error *)
IF bAmphosOn AND NOT bBaseplateChillerFlow THEN
    bError := TRUE;
END_IF

IF bMasterLatchReset THEN
    bError := FALSE;
END_IF

END_FUNCTION_BLOCK

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

FB_ChillerLoop

// Monitors the chiller loop from the flow sensor
FUNCTION_BLOCK FB_ChillerLoop
VAR_INPUT
    bLoopTempSW : BOOL; // NC Temperature switch. Indicates when temperature is too high
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 := FALSE; // Whether an error was detected. Goes back to normal on its own
    {attribute 'pytmc' := '
        pv: OVERRIDE
        io: io
        field: ZNAM "OFF"
        field: ONAM "ON"
    '}
    bOperatorOverride : BOOL := FALSE; // If TRUE, allows the EPS to keep running despite the chiller loop error
END_VAR
(* Set error flag if loop temperature monitor detects an error *)
bError := NOT bLoopTempSW;

(* Reset the operator override if the system returns to normal tempreature range *)
IF bLoopTempSW THEN
    bOperatorOverride := FALSE;
END_IF

END_FUNCTION_BLOCK

FB_DumpChiller

// Monitors the dump chiller from the flow sensor
FUNCTION_BLOCK FB_DumpChiller
VAR_INPUT
    bAmphosOn : BOOL; // Whether the amphos is turned on
    bDumpChillerFlow : BOOL; // Signal from the flow sensor. Normally high.
    bMasterLatchReset : BOOL; //Master latched error reset
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; // Whether an error was detected. Latches until reset
END_VAR
VAR
END_VAR
(* Latch error if the dump chiller flow detects an error and the amphos is on *)
IF bAmphosOn AND NOT bDumpChillerFlow THEN
    bError := TRUE;
END_IF

IF bMasterLatchReset THEN
    bError := FALSE;
END_IF

END_FUNCTION_BLOCK

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

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_INPUT
    nLeakLocations : BYTE;
END_VAR
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;
    fbLeakOpticalTableAmphosOnTrig : R_TRIG;
    fbLeakOpticalTableNoLaserTrig : R_TRIG;
    fbLeakOpcpaCarbideOnTrig : R_TRIG;
    fbLeakUnderTableTrig : R_TRIG;
    fbLeakInsideRacksTrig : R_TRIG;
    fbAmphosBeamTrig : R_TRIG;
    fbOpcpaBeamTrig : R_TRIG;
    fbDumpChillerTrig : R_TRIG;
    fbBaseplateChillerTrig : R_TRIG;
END_VAR
fbLeakTrig(
    CLK := nLeakLocations <> E_LeakLocation.NO_LEAK,
);
fbLeakOpticalTableAmphosOnTrig(
    CLK := stErrors.bLeakOpticalTableAmphosOn,
    Q => stNewErrors.bLeakOpticalTableAmphosOn
);
fbLeakOpticalTableNoLaserTrig(
    CLK := stErrors.bLeakOpticalTableNoLaser,
    Q => stNewErrors.bLeakOpticalTableNoLaser
);
fbLeakOpcpaCarbideOnTrig(
    CLK := stErrors.bLeakOpcpaCarbideOn,
    Q => stNewErrors.bLeakOpcpaCarbideOn
);
fbLeakUnderTableTrig(
    CLK := stErrors.bLeakUnderTable,
    Q => stNewErrors.bLeakUnderTable
);
fbLeakInsideRacksTrig(
    CLK := stErrors.bLeakInsideRacks,
    Q => stNewErrors.bLeakInsideRacks
);

fbAmphosBeamTrig(
    CLK := stErrors.bAmphosBeam,
    Q => stNewErrors.bAmphosBeam
);
fbOpcpaBeamTrig(
    CLK := stErrors.bOpcpaBeam,
    Q => stNewErrors.bOpcpaBeam
);

fbDumpChillerTrig(
    CLK := stErrors.bDumpChiller,
    Q => stNewErrors.bDumpChiller
);
fbBaseplateChillerTrig(
    CLK := stErrors.bBaseplateChiller,
    Q => stNewErrors.bBaseplateChiller
);

END_FUNCTION_BLOCK

// Returns whether a rising-edge leak error has occured
METHOD NewLeakError : BOOL
VAR_INPUT
END_VAR
NewLeakError := fbLeakTrig.Q OR fbLeakOpticalTableAmphosOnTrig.Q OR fbLeakOpticalTableNoLaserTrig.Q OR fbLeakOpcpaCarbideOnTrig.Q OR fbLeakUnderTableTrig.Q OR fbLeakInsideRacksTrig.Q;
END_METHOD

// Reset latched amphos errors
METHOD ResetAmphosTrigger
VAR_INPUT
END_VAR
fbAmphosBeamTrig(
    CLK := FALSE,
    Q => stNewErrors.bAmphosBeam
);
END_METHOD

METHOD ResetBaseplateChillerTrigger
VAR_INPUT
END_VAR
fbBaseplateChillerTrig(
    CLK := FALSE,
    Q => stNewErrors.bBaseplateChiller
);
END_METHOD

METHOD ResetDumpChillerTrigger
VAR_INPUT
END_VAR
fbDumpChillerTrig(
    CLK := FALSE,
    Q => stNewErrors.bDumpChiller
);
END_METHOD

// Reset latched leak errors
METHOD ResetLeakTriggers
VAR_INPUT
END_VAR
fbLeakOpticalTableAmphosOnTrig(
    CLK := FALSE,
    Q => stNewErrors.bLeakOpticalTableAmphosOn
);
fbLeakOpticalTableNoLaserTrig(
    CLK := FALSE,
    Q => stNewErrors.bLeakOpticalTableNoLaser
);
fbLeakOpcpaCarbideOnTrig(
    CLK := FALSE,
    Q => stNewErrors.bLeakOpcpaCarbideOn
);
fbLeakUnderTableTrig(
    CLK := FALSE,
    Q => stNewErrors.bLeakUnderTable
);
fbLeakInsideRacksTrig(
    CLK := FALSE,
    Q => stNewErrors.bLeakInsideRacks
);
END_METHOD

// Reset latched OPCPA errors
METHOD ResetOpcpaTrigger
VAR_INPUT
END_VAR
fbOpcpaBeamTrig(
    CLK := FALSE,
    Q => stNewErrors.bOpcpaBeam
);
END_METHOD
Related:

FB_LaserShutterInterlock

FUNCTION_BLOCK FB_LaserShutterInterlock
VAR_INPUT
    //bAmphosOn : BOOL; // Whether the amphos is on
    fOpcpaVoltage       : LREAL; // The voltage of the OPCPA
    fAmphosVoltage      : LREAL; // The Amphos voltage
    fMPCVoltage         : LREAL; // The MPC voltage
    AmphosStatus        : BOOL; //Amphos Laser Shutter Status
    bLoopTempOverride01 : BOOL; // Whether the chiller loop error is overriden
    bLoopTempOverride02 : BOOL; // Whether the chiller loop error is overriden
    bLoopTempOverride03 : BOOL; // Whether the chiller loop error is overriden
    bLoopTempOverride04 : BOOL; // Whether the chiller loop error is overriden
//    bLoopTempOverride05 : BOOL; // Whether the chiller loop error is overriden
//    bShutterStatus      : BOOL; // Shutter Status
END_VAR

VAR_IN_OUT CONSTANT
    stErrors            : ST_ErrorStates; // Currently detected errors
    stOpcpaSetpoints    : ST_LaserSetpoints; // OPCPA setpoints
    stAmphosSetpoints   : ST_LaserSetpoints; // The Amphos setpoints
    stMPCSetpoints      : ST_LaserSetpoints; // The MPC setpoints
END_VAR

VAR_IN_OUT
    Mode              : E_Mode; // Mode
    bAmphosShutter   : BOOL; // Allowed Amphos Shutter state, point to NC Relay GVL
END_VAR

VAR
    bChillerErrors      : BOOL; // Local logical sum of chiller error states
    bTempSwitchErrors   : BOOL; // Local logical sum of temperature switch errors
    bAmphosUnderVolt    : BOOL; // Local var for checking Amphos under MinVoltage
    bOpcpaUnderVolt     : BOOL; // Local var for checking OPCPA under MinVoltage
    bMpcUnderVolt       : BOOL; // Local var for checking MPC under MinVoltage
END_VAR

VAR_OUTPUT
    bCarbideShutter  : BOOL; // Allowed Carbide Shutter state, point to NC Relay GVL
END_VAR
(*
The Amphos Shutter Enable function sends and Enable or Disable bit as an output to be used by the Amphos Shutter Command block
AmphosShutterEnable := TRUE := OPEN SHUTTER
AmphosShutterEnable := FALSE := CLOSE SHUTTER

Note : In master override modes, the Shutter state will hold the previous state value, unless manually forced
GVL_IO.bAmphosShutterEnable is the manual override PV for the shutter enable
*)

bChillerErrors := (
                 (NOT bLoopTempOverride01 AND stErrors.bChillerLoop01) OR
                 (NOT bLoopTempOverride02 AND stErrors.bChillerLoop02) OR
                 (NOT bLoopTempOverride03 AND stErrors.bChillerLoop03) OR
                 (NOT bLoopTempOverride04 AND stErrors.bChillerLoop04)
//                 OR (NOT bLoopTempOverride05 AND stErrors.bChillerLoop05)
                 );
bTempSwitchErrors := (
                     NOT GVL_IO.bLoopTempSW01 OR
                     NOT GVL_IO.bLoopTempSW02 OR
                     NOT GVL_IO.bLoopTempSW03 OR
                     NOT GVL_IO.bLoopTempSW04 OR
                     NOT GVL_IO.bLoopTempSW05
                     );

bAmphosUnderVolt := fAmphosVoltage < stAmphosSetpoints.nMinVoltage;
bOpcpaUnderVolt := fOpcpaVoltage < stOpcpaSetpoints.nMinVoltage;
bMpcUnderVolt := fMPCVoltage < stMPCSetpoints.nMinVoltage;

CASE MODE OF
    // With great overrides comes great responsibility, and burnt optics
    E_Mode.MASTER_Override:
// Just pass and retain the previous Amphos Shutter State
        bCarbideShutter := TRUE;
        bAmphosShutter := GVL_IO.bAmphosShutterManualOverride;
        GVL_IO.bAmphosRelay := TRUE;

    E_Mode.Protection:
        // Close shutter if there is a hardware failure
        IF (stErrors.bHardwareFailure
        // Close shutter if there is a leak on the optical table with the amphos on, a leak under the table, or a leak inside the OPCPA with Carbide on
        (*OR stErrors.bLeakOpticalTableAmphosOn OR stErrors.bLeakOpcpaCarbideOn OR stErrors.bLeakUnderTable*)
        //Close shutter if the amphos is on and the temperature is too high in any loop. Ignore if operator override is enabled
         OR (bChillerErrors OR bTempSwitchErrors)
        // Close shutter if in OPCPA beam error state and OPCPA voltage too low
         OR (bOpcpaUnderVolt AND stErrors.bOpcpaBeam)
        // Close the shutter if there is a Dump Chiller error
         OR stErrors.bDumpChiller
        )
        // Check global IO for redundancy of Dump Chiller Errors
        OR (NOT GVL_IO.bDumpChillerFlow)
        OR (NOT GVL_IO.bBaseplateChillerFlow)
        THEN
            bAmphosShutter := bCarbideShutter := GVL_IO.bAmphosRelay := FALSE;
        ELSE
            // If the Amphos trips, always disable it
            IF bAmphosUnderVolt
            THEN
                //GVL_IO.bAmphosShutterEnableManualOperation MUST be overwritten to update EPICS downstream
                bAmphosShutter := GVL_IO.bAmphosShutterManualOverride := FALSE;
                GVL_IO.bAmphosRelay:= FALSE;
            ELSE
                bAmphosShutter := GVL_IO.bAmphosRelay := TRUE;
            END_IF
            // Only open the Amphos shutter if all 3 lasers are happy
            IF (NOT(bAmphosUnderVolt OR bOpcpaUnderVolt OR bMpcUnderVolt))
            THEN
                //GVL_IO.bAmphosShutterEnable MUST be overwritten to update EPICS downstream
                bAmphosShutter := GVL_IO.bAmphosShutterManualOverride := TRUE;
            ELSE
                //GVL_IO.bAmphosShutterEnable MUST be overwritten to update EPICS downstream
                bAmphosShutter := GVL_IO.bAmphosShutterManualOverride := FALSE;
            END_IF
            // Carbide Shutter is weird, allows solo Carbide use explicitly
            IF NOT (bAmphosUnderVolt OR bOpcpaUnderVolt OR bMpcUnderVolt)
                // This state is physically impossible
                OR (bAmphosUnderVolt AND NOT bOpcpaUnderVolt AND NOT bMpcUnderVolt)
                // But this state happens when the AMPHOS is powered down
                OR (bAmphosUnderVolt AND bOpcpaUnderVolt AND NOT bMpcUnderVolt)
            THEN
                bCarbideShutter := TRUE;
            ELSE
                bCarbideShutter := FALSE;
            END_IF
        END_IF

    E_Mode.AMPHOS_Maintenance:
    // Close shutter if there is a hardware failure
        IF (stErrors.bHardwareFailure
        // Close shutter if there is a leak on the optical table with the amphos on, a leak under the table, or a leak inside the OPCPA with Carbide on
        (*OR stErrors.bLeakOpticalTableAmphosOn OR stErrors.bLeakOpcpaCarbideOn OR stErrors.bLeakUnderTable*)
        //Close shutter if the amphos is on and the temperature is too high in any loop. Ignore if operator override is enabled
         OR (bChillerErrors OR bTempSwitchErrors)
        // Close the shutter if there is a Dump Chiller error
         OR stErrors.bDumpChiller
        )
        // Check global IO for redundancy of Chiller Errors
        OR (NOT GVL_IO.bDumpChillerFlow)
        OR (NOT GVL_IO.bBaseplateChillerFlow)
        THEN
            //GVL_IO.bAmphosShutterEnable MUST be overwritten to update EPICS downstream
            bAmphosShutter := bCarbideShutter := GVL_IO.bAmphosShutterManualOverride := FALSE;
            GVL_IO.bAmphosRelay := FALSE;
        ELSE
            // Just pass the Amphos shutter state and keep it enabled
            //GVL_IO.bAmphosShutterEnable MUST be overwritten to update EPICS downstream
            GVL_IO.bAmphosRelay := TRUE;
            bAmphosShutter := GVL_IO.bAmphosShutterManualOverride;
        END_IF
    // Close the carbide shutter if the MPC is unhappy
    IF bMpcUnderVolt
    THEN
        bCarbideShutter := FALSE;
    ELSE
        bCarbideShutter := TRUE;
    END_IF

    // Undefined state, do not get here. Do not pass go, do not collect 200 dollars
    ELSE
    // If you get stuck here you deserve it
        Mode := E_Mode.Protection;
END_CASE

END_FUNCTION_BLOCK
Related:

FB_LaserTimer

// Monitors the lasers for under voltage events
FUNCTION_BLOCK FB_LaserTimer
VAR_INPUT
    nVoltageRaw : INT; // The raw signal collected from the ADC
    bAmphosOn : BOOL; // Whether the amphos is turned on
    Timer : TON ; //Timer for reset
    bMasterLatchReset : BOOL; //Master latched error reset
    bCarbideOn : BOOL; // Whether Carbide is ON
    Mode : E_Mode;
END_VAR
VAR_IN_OUT CONSTANT
    stSetpoints : ST_LaserSetpoints; // Setpoints for the laser
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: VOLTAGE
        io: i
        field: HOPR 10.0
        field: LOPR 0.0
        field: LSV MINOR
        field: LLSV MAJOR
        field: EGU Volts
    '}
    fVoltage : LREAL; // Voltage calculated from the raw ADC input

    {attribute 'pytmc' := '
        pv: SCALED_PWR
        io: i
        field: EGU Watts
    '}
    fPower : LREAL;

    {attribute 'pytmc' := '
        pv: ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bError : BOOL; // Whether an error was detected. Latches until reset
    bBelowNominal : BOOL; //fVoltage is below the nominal setpoint
END_VAR
(* Scale the analog signal to voltage *)
fVoltage := F_Scale(
    fRawData := nVoltageRaw,
    fRawDataLowerOffLimit := GVL_EL3174.fRawLowerLimit,
    fRawDataUpperOffLimit := GVL_EL3174.fRawUpperLimit,
    fScaleDataLowerOffLimit := GVL_EL3174.fScaleLowerLimit,
    fScaleDataUpperOffLimit := GVL_EL3174.fScaleUpperLimit);

fPower := fVoltage; //Value is scaled by user set ASLO and AOFF fields (for display only)

(* Set error if amphos is below nominal voltage *)
IF  (bAmphosOn AND fVoltage < stSetpoints.nMinVoltage) THEN
    Timer(IN:=TRUE, PT:=T#500ms); //500ms SEC Timer
    IF Timer.Q THEN
        bError := TRUE;
        Timer(IN:=FALSE);
    END_IF;
ELSE
    Timer(IN:=FALSE);
END_IF

IF bAmphosOn AND (fVoltage < stSetpoints.nMinNominalVoltage) THEN
    bBelowNominal := TRUE;
END_IF

IF bMasterLatchReset THEN
    bBelowNominal := FALSE;
    bError := FALSE;
END_IF

END_FUNCTION_BLOCK

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

FB_LaserTimer_MPC

// Monitors the lasers for under voltage events
FUNCTION_BLOCK FB_LaserTimer_MPC
VAR_INPUT
    nVoltageRaw : INT; // The raw signal collected from the ADC
    bCarbideOn : BOOL; // Whether the amphos is turned on
    Timer : TON ; //Timer for reset
    bMasterLatchReset : BOOL; //Master latched error reset
END_VAR
VAR_IN_OUT CONSTANT
    stSetpoints : ST_LaserSetpoints; // Setpoints for the laser
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: VOLTAGE
        io: i
        field: HOPR 10.0
        field: LOPR 0.0
        field: LSV MINOR
        field: LLSV MAJOR
        field: EGU Volts
    '}
    fVoltage : LREAL; // Voltage calculated from the raw ADC input

    {attribute 'pytmc' := '
        pv: SCALED_PWR
        io: i
        field: EGU Watts
    '}
    fPower : LREAL;

    {attribute 'pytmc' := '
        pv: ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bError : BOOL; // Whether an error was detected. Latches until reset
    bBelowNominal : BOOL; //fVoltage is below the nominal setpoint
END_VAR
(* Scale the analog signal to voltage *)
fVoltage := F_Scale(
    fRawData := nVoltageRaw,
    fRawDataLowerOffLimit := GVL_EL3174.fRawLowerLimit,
    fRawDataUpperOffLimit := GVL_EL3174.fRawUpperLimit,
    fScaleDataLowerOffLimit := GVL_EL3174.fScaleLowerLimit,
    fScaleDataUpperOffLimit := GVL_EL3174.fScaleUpperLimit);

fPower := fVoltage; //Value is scaled by user set ASLO and AOFF fields (for display only)

(* Set error if amphos is below nominal voltage *)
IF  (bCarbideOn AND fVoltage < stSetpoints.nMinVoltage) THEN
    Timer(IN:=TRUE, PT:=T#500ms); //500ms SEC timer
    IF Timer.Q THEN
        bError := TRUE;
        Timer(IN:=FALSE); // Reset Timer
    END_IF;
ELSE
    Timer(IN:=FALSE);
END_IF

IF bCarbideOn AND (fVoltage < stSetpoints.nMinNominalVoltage) THEN
    bBelowNominal := TRUE;
END_IF

IF bMasterLatchReset THEN
    bBelowNominal := FALSE;
    bError := FALSE;
END_IF

END_FUNCTION_BLOCK

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

FB_LeakMonitor

// Detect leaks and leak locations
FUNCTION_BLOCK FB_LeakMonitor
VAR_INPUT
    bLeakDetected : BOOL; // Whether a leak was detected
    bAmphosOn : BOOL; // Whether the Amphos is on
    bCarbideOn : BOOL; // Whether the Carbide is on
    nLocationResistance: UINT; // Used for calculating the location of the leak
    bMasterLatchReset : BOOL; //Master latched error reset
END_VAR
VAR_IN_OUT CONSTANT
    stBound : ST_LeakBoundaries; // Setpoints for calculating leak locations from location resistance
END_VAR
VAR_OUTPUT

    {attribute 'pytmc' := '
        pv: LOW_RES
        io: i
    '}
    bLowResistance : BOOL := FALSE;

    {attribute 'pytmc' := '
        pv: LEAKS
        io: i
    '}
    nLeakLocations : BYTE := E_LeakLocation.NO_LEAK; // Locations of leaks
    {attribute 'pytmc' := '
        pv: LOTAO_ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bLeakOpticalTableAmphosOn : BOOL := FALSE; // Leak on the optical table with Amphos on
    {attribute 'pytmc' := '
        pv: LOTNL_ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bLeakOpticalTableNoLaser : BOOL := FALSE; // Leak on the optical table with no laser on
    {attribute 'pytmc' := '
        pv: LOCO_ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bLeakOpcpaCarbideOn : BOOL := FALSE; // Leak inside the OPCPA with Carbide on
    {attribute 'pytmc' := '
        pv: LUT_ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bLeakUnderTable : BOOL := FALSE; // Leak under the table
    {attribute 'pytmc' := '
        pv: LIR_ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bLeakInsideRacks : BOOL := FALSE; // Leak inside the racks
END_VAR
IF (nLocationResistance < 200) THEN
    bLowResistance := TRUE;
ELSE
    bLowResistance := FALSE;
END_IF

IF (bLeakDetected OR bLowResistance) THEN
    (* Find leak location for PV. Stays until reset *)
    IF stBound.nLMin1 <= nLocationResistance AND nLocationResistance < stBound.nLMax1 THEN
        nLeakLocations := nLeakLocations OR E_LeakLocation.LOC1;
    END_IF

    IF stBound.nLMin2 <= nLocationResistance AND nLocationResistance < stBound.nLMax2 THEN
        nLeakLocations := nLeakLocations OR E_LeakLocation.LOC2;
    END_IF

    IF stBound.nLMin3 <= nLocationResistance AND nLocationResistance < stBound.nLMax3 THEN
        nLeakLocations := nLeakLocations OR E_LeakLocation.LOC3;
    END_IF

    IF stBound.nLMin4 <= nLocationResistance AND nLocationResistance < stBound.nLMax4 THEN
        nLeakLocations := nLeakLocations OR E_LeakLocation.LOC4;
    END_IF

    IF stBound.nLMin5 <= nLocationResistance AND nLocationResistance < stBound.nLMax5 THEN
        nLeakLocations := nLeakLocations OR E_LeakLocation.LOC5;
    END_IF

    IF stBound.nLMin6 <= nLocationResistance AND nLocationResistance < stBound.nLMax6 THEN
        nLeakLocations := nLeakLocations OR E_LeakLocation.LOC6;
    END_IF

    //CURRENT SETUP : LOC1 = OPCPA, LOC2 = rest of optical table (values decrease toward higher LOC#)
    (* Latch Optical table leak errors *)
    IF (stBound.nLMin2 <= nLocationResistance AND nLocationResistance < stBound.nLMax1) THEN
        IF bAmphosOn THEN
            bLeakOpticalTableAmphosOn := TRUE;
        ELSIF NOT bCarbideOn THEN
            bLeakOpticalTableNoLaser := TRUE;
        END_IF
    END_IF

    (* Latch carbide leak errors *)
    IF (bCarbideOn AND stBound.nLMin1 <= nLocationResistance AND nLocationResistance < stBound.nLMax1) THEN
        bLeakOpcpaCarbideOn := TRUE;
    END_IF

    (* Latch leak under table errors *)
    IF (stBound.nLMin5 <= nLocationResistance AND nLocationResistance < stBound.nLMax5) THEN
        bLeakUnderTable := TRUE;
    END_IF

    (* Latch leak inside rack errors *)
    IF (stBound.nLMin6 <= nLocationResistance AND nLocationResistance < stBound.nLMax6) THEN
        bLeakInsideRacks := TRUE;
    END_IF

END_IF

IF bMasterLatchReset THEN
    nLeakLocations              := E_LeakLocation.NO_LEAK;
    bLeakOpticalTableAmphosOn   := FALSE;
    bLeakOpcpaCarbideOn         := FALSE;
    bLeakOpticalTableNoLaser    := FALSE;
    bLeakUnderTable             := FALSE;
    bLeakInsideRacks            := FALSE;
END_IF

END_FUNCTION_BLOCK

// Reset latched errors
METHOD Reset
VAR_INPUT
END_VAR
nLeakLocations := E_LeakLocation.NO_LEAK;
bLeakOpticalTableAmphosOn := FALSE;
bLeakOpticalTableNoLaser := FALSE;
bLeakOpcpaCarbideOn := FALSE;
bLeakUnderTable := FALSE;
bLeakInsideRacks := FALSE;
END_METHOD
Related:

FB_TraceTekModbus

// Communicates with the TraceTek TTSIM-1A hardware over Modbus to get more information about a leak
FUNCTION_BLOCK FB_TraceTekModbus
VAR_INPUT
    nModbusSlaveAddress : BYTE; // Modbus address of slave to be read from
    bLeakDetected : BOOL; // Whether a leak has been detected
    bResetError : BOOL; // Reset signal for error
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: LOC_RES
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    nLocationResistance: UINT; // Resistance measured in the detection wire
    {attribute 'pytmc' := '
        pv: ERROR
        io: i
        field: OSV MAJOR
        field: ZSV NO_ALARM
        field: ZNAM OK
        field: ONAM ERROR
    '}
    bError : BOOL := FALSE; // Flag for Modbus communication errors
    nErrorId : MODBUS_ERRORS := MODBUS_ERRORS.MODBUSERROR_NO_ERROR; // Description of Modbus error
END_VAR
VAR
    aModbusBuffer : WORD := 16#FFFF;  // "Infinite" until real value is read from device
    fbModbusMaster : ModbusRtuMaster_KL6x22B := (
        Quantity := 1,
        MBAddr := 1,
        cbLength := SIZEOF(aModbusBuffer),
        pMemoryAddr := ADR(aModbusBuffer),
        Timeout := T#5S
    );
    fbPoll : TON := (PT := T#0S); // Poll rate for reading modbus. Start at 0 so that modbus is read on first call
END_VAR
VAR_STAT
    tPollRate : TIME := T#500MS;
END_VAR
// Advance poll rate timer
fbPoll(IN := TRUE);

// Update modbus status
fbModbusMaster.Execute := FALSE;
fbModbusMaster.ReadInputRegs();

IF bResetError THEN // Reset modbus error
    bError := FALSE;
    nErrorId := MODBUS_ERRORS.MODBUSERROR_NO_ERROR;
ELSIF fbModbusMaster.Error OR NOT fbModbusMaster.BUSY THEN
    // Allow fb error state to go back to normal by checking modbus error state after reading has finished
    bError := fbModbusMaster.Error;
    nErrorId := fbModbusMaster.ErrorId;
END_IF

(* If the modbus interface isn't busy and there is a leak, try reading
the leak location from the TraceTek. Even if there is no leak, maintain
the modbus connection by checking every few seconds using the fbPoll rate *)
IF NOT fbModbusMaster.BUSY AND (bLeakDetected OR fbPoll.Q) THEN
    fbPoll(IN := FALSE, PT := tPollRate);
    fbModbusMaster.UnitID := nModbusSlaveAddress;
    fbModbusMaster.Execute := TRUE;
    fbModbusMaster.ReadInputRegs();
END_IF

// Copy the location resitance from the buffer to the FB output
nLocationResistance := WORD_TO_UINT(aModbusBuffer);

END_FUNCTION_BLOCK

MAIN

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

    // Leak Monitor
    {attribute 'pytmc' := 'pv: @(PREFIX):TTSIM:DEV01'}
    fbTraceTekModbus : FB_TraceTekModbus; // Modbus connection to TraceTek
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:LEAK:RESET'}
    bLeakMonitorReset : BOOL; // Leak monitor reset flag
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:LEAK'}
    fbLeakMonitor : FB_LeakMonitor; // Leak detection

    // Chiller Loop temperature monitors
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMP01'}
    fbChillerLoop01 : FB_ChillerLoop; // Chiller loop 01
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMP02'}
    fbChillerLoop02 : FB_ChillerLoop; // Chiller loop 02
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMP03'}
    fbChillerLoop03 : FB_ChillerLoop; // Chiller loop 03
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMP04'}
    fbChillerLoop04 : FB_ChillerLoop; // Chiller loop 04
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMP05'}
    fbChillerLoop05 : FB_ChillerLoop; // Chiller loop 05


    // Laser monitors
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:AMPHOS'}
    fbAmphos : FB_LaserTimer; // Amphos laser
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:AMPHOS:RESET
        io: o
    '}
    bAmphosReset : BOOL; // Amphos laser error unlatch flag
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:OPCPA'}
    fbOpcpa : FB_LaserTimer; // OPCPA laser
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:OPCPA:RESET
        io: o
    '}
    bOpcpaReset : BOOL; // OPCPA laser error unlatch flag
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:MPC'}
    fbMpc : FB_LaserTimer_MPC; // MPC laser
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:MPC:RESET
        io: o
    '}
    bMpcReset : BOOL; // MPC laser error unlatch flag


    (*
    // Temperature monitors
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON01'}
    fbTempMon01 : FB_TemperatureMonitor; // System temperature monitor 01
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON02'}
    fbTempMon02 : FB_TemperatureMonitor; // System temperature monitor 02
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON03'}
    fbTempMon03 : FB_TemperatureMonitor; // System temperature monitor 03
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON04'}
    fbTempMon04 : FB_TemperatureMonitor; // System temperature monitor 04
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON05'}
    fbTempMon05 : FB_TemperatureMonitor; // System temperature monitor 05
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON06'}
    fbTempMon06 : FB_TemperatureMonitor; // System temperature monitor 06
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON07'}
    fbTempMon07 : FB_TemperatureMonitor; // System temperature monitor 07
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:TEMPMON08'}
    fbTempMon08 : FB_TemperatureMonitor; // System temperature monitor 08
    *)

    // Chillers
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:DUMP_CHILLER'}
    fbDumpChiller : FB_DumpChiller; // Flow sensor monitor for the dump chiller
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:DUMP_CHILLER:RESET
        io: o
    '}
    bDumpChillerReset : BOOL; // Dump chiller error unlatch flag

    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:BASEPLATE_CHILLER'}
    fbBaseplateChiller : FB_BaseplateChiller; // Flow sensor monitor for the baseplate chiller
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:BASEPLATE_CHILLER:RESET
        io: o
    '}
    bBaseplateChillerReset : BOOL; // Baseplate chiller error unlatch flag

    (*
    // Amphos MRCs
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:AMPHOS_MRC01'}
    fbAmphosMRC01 : FB_AmphosMRC; // MRC monitor 01
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:AMPHOS_MRC02'}
    fbAmphosMRC02 : FB_AmphosMRC; // MRC monitor 02
    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:AMPHOS_MRC03'}
    fbAmphosMRC03 : FB_AmphosMRC; // MRC monitor 03
    *)

    (*
    This is the map of the Mode Int to the defintion
    MODE = 1 ==> Master Override
    MODE = 2 ==> Protection
    MODE = 3 ==> Amphos Maintenance
    Any other mode is invalid.
    *)
    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:MODE
        io: io
        field: NOBT 3
    '}
    Mode_LPS : E_Mode; // Mode Definition

    fbLaserShutterInterlocks : FB_LaserShutterInterlock; // Laser shutter interlock handler

    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:MASTER_OVERRIDE
        io: o
    '}
    bMasterOverride : BOOL := TRUE; // Master override control for safety system.

    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:MASTER_AMP_SHUTTER
        io: o
    '}
    bAmpShutterClosed : BOOL; // Master Amphos Shutter control

    {attribute 'pytmc' := '
        pv: @(PREFIX):LPS:MASTER_LATCH_RESET
        io: o
    '}
    bMasterLatchReset : BOOL; //Master release of latched error conditions

    bLatchStart : BOOL := FALSE; //Latch at start of loop.

    {attribute 'pytmc' := 'pv: @(PREFIX):LPS:PLC'}
    fbDiag : FB_EcatDiag; // Ethercat diagnostics
END_VAR
(* Ensure master latch is TRUE for at least one full cycle of the PLC logic *)
IF bMasterLatchReset AND NOT bLatchStart THEN
    bLatchStart := TRUE;
END_IF

(*******************************************************************
        Update diagnostics. Check for hardware failures
*******************************************************************)
(* Check PLC hardware for failures *)
fbDiag(AMSNetId := GVL_PLC.AMSNetId);
stErrors.bHardwareFailure := (fbDiag.bError OR fbTraceTekModbus.bError) AND NOT bMasterOverride;

(*******************************************************************
Feed each device function block inputs from the PLC IO. Collect error states detected
by the function blocks into stErrors. These collected errors are then used later for
making decisions in the interlock logic.
*******************************************************************)
(* Detects leaks using the location resitance from the TraceTek *)
fbLeakMonitor(
    nLocationResistance             := fbTraceTekModbus.nLocationResistance,
    bAmphosOn                       := GVL_IO.bAmphosOn,
    bCarbideOn                      := GVL_IO.bCarbideOn,
    bLeakDetected                   := GVL_IO.bLeakDetected,
    stBound                         := GVL_TraceTek.stLeakMonitorBoundaries,
    bLeakOpticalTableAmphosOn       => stErrors.bLeakOpticalTableAmphosOn,
    bLeakOpticaltableNoLaser        => stErrors.bLeakOpticalTableNoLaser,
    bLeakOpcpaCarbideOn             => stErrors.bLeakOpcpaCarbideOn,
    bLeakUnderTable                 => stErrors.bLeakUnderTable,
    bLeakInsideRacks                => stErrors.bLeakInsideRacks,
    bMasterLatchReset               := bMasterLatchReset
);

(* Monitors for error events on chiller loop 1 *)
fbChillerLoop01(
    bLoopTempSW     := GVL_IO.bLoopTempSW01,
    bError          => stErrors.bChillerLoop01
);

(* Monitors for error events on chiller loop 2 *)
fbChillerLoop02(
    bLoopTempSW     := GVL_IO.bLoopTempSW02,
    bError          => stErrors.bChillerLoop02
);

(* Monitors for error events on chiller loop 3 *)
fbChillerLoop03(
    bLoopTempSW     := GVL_IO.bLoopTempSW03,
    bError          => stErrors.bChillerLoop03
);

(* Monitors for error events on chiller loop 4 *)
fbChillerLoop04(
    bLoopTempSW     := GVL_IO.bLoopTempSW04,
    bError          => stErrors.bChillerLoop04
);

(* Monitors for error events on chiller loop 5 *)
fbChillerLoop05(
    bLoopTempSW     := GVL_IO.bLoopTempSW05,
    bError          => stErrors.bChillerLoop05
);

(* Monitors the Amphos laser *)
fbAmphos(
    nVoltageRaw         := GVL_IO.nAmphosVoltageRaw,
    bAmphosOn           := GVL_IO.bAmphosOn,
    stSetpoints         := GVL_Laser.stAmphosSP,
    bError              => stErrors.bAmphosBeam,
    bMasterLatchReset   := bMasterLatchReset
);

(* Monitors the OPCPA laser *)
fbOpcpa(
    nVoltageRaw         := GVL_IO.nOpcpaVoltageRaw,
    bAmphosOn           := GVL_IO.bAmphosOn,
    stSetpoints         := GVL_Laser.stOpcpaSP,
    bError              => stErrors.bOpcpaBeam,
    bMasterLatchReset   := bMasterLatchReset
);

fbMpc(
    nVoltageRaw         := GVL_IO.nMpcVoltageRaw,
    bCarbideOn           := GVL_IO.bAmphosOn,
    stSetpoints         := GVL_Laser.stMpcSP,
    bError              => stErrors.bMpcBeam,
    bMasterLatchReset   := bMasterLatchReset
);


(*
(* Monitors system temperature from sensor 1 *)
fbTempMon01(
    nTempMonRaw := GVL_IO.nTempMonRaw01,
    bAmphosOn := GVL_IO.bAmphosOn,
    stSetpoints := GVL_TemperatureMonitor.stTempMonSP01,
    bError => stErrors.bTempMon01
);

(* Monitors system temperature from sensor 2 *)
fbTempMon02(
    nTempMonRaw := GVL_IO.nTempMonRaw02,
    bAmphosOn := GVL_IO.bAmphosOn,
    stSetpoints := GVL_TemperatureMonitor.stTempMonSP02,
    bError => stErrors.bTempMon02
);

(* Monitors system temperature from sensor 3 *)
fbTempMon03(
    nTempMonRaw := GVL_IO.nTempMonRaw03,
    bAmphosOn := GVL_IO.bAmphosOn,
    stSetpoints := GVL_TemperatureMonitor.stTempMonSP03,
    bError => stErrors.bTempMon03
);

(* Monitors system temperature from sensor 4 *)
fbTempMon04(
    nTempMonRaw := GVL_IO.nTempMonRaw04,
    bAmphosOn := GVL_IO.bAmphosOn,
    stSetpoints := GVL_TemperatureMonitor.stTempMonSP04,
    bError => stErrors.bTempMon04
);

(* Monitors system temperature from sensor 5 *)
fbTempMon05(
    nTempMonRaw := GVL_IO.nTempMonRaw05,
    bAmphosOn := GVL_IO.bAmphosOn,
    stSetpoints := GVL_TemperatureMonitor.stTempMonSP05,
    bError => stErrors.bTempMon05
);

(* Monitors system temperature from sensor 6 *)
fbTempMon06(
    nTempMonRaw := GVL_IO.nTempMonRaw06,
    bAmphosOn := GVL_IO.bAmphosOn,
    stSetpoints := GVL_TemperatureMonitor.stTempMonSP06,
    bError => stErrors.bTempMon06
);

(* Monitors system temperature from sensor 7 *)
fbTempMon07(
    nTempMonRaw := GVL_IO.nTempMonRaw07,
    bAmphosOn := GVL_IO.bAmphosOn,
    stSetpoints := GVL_TemperatureMonitor.stTempMonSP07,
    bError => stErrors.bTempMon07
);

(* Monitors system temperature from sensor 8 *)
fbTempMon08(
    nTempMonRaw := GVL_IO.nTempMonRaw08,
    bAmphosOn := GVL_IO.bAmphosOn,
    stSetpoints := GVL_TemperatureMonitor.stTempMonSP08,
    bError => stErrors.bTempMon08
);
*)

(* Flow sensor monitor for the dump chiller *)
fbDumpChiller(
    bAmphosOn           := GVL_IO.bAmphosOn,
    bDumpChillerFlow    := GVL_IO.bDumpChillerFlow,
    bError              => stErrors.bDumpChiller,
    bMasterLatchReset   := bMasterLatchReset
);

(* Flow sensor monitor for the baseplate chiller *)
fbBaseplateChiller(
    bAmphosOn               := GVL_IO.bAmphosOn,
    bBaseplateChillerFlow   := GVL_IO.bBaseplateChillerFlow,
    bError                  => stErrors.bBaseplateChiller,
    bMasterLatchReset       := bMasterLatchReset
);

(*
(* Detects problems with MRC 1 *)
fbAmphosMRC01(
    bAmphosOn := GVL_IO.bAmphosOn,
    bMRC := GVL_IO.bAmphosMRC01,
    bError => stErrors.bAmphosMRC01
);

(* Detects problems with MRC 2 *)
fbAmphosMRC02(
    bAmphosOn := GVL_IO.bAmphosOn,
    bMRC := GVL_IO.bAmphosMRC02,
    bError => stErrors.bAmphosMRC02
);

(* Detects problems with MRC 3 *)
fbAmphosMRC03(
    bAmphosOn := GVL_IO.bAmphosOn,
    bMRC := GVL_IO.bAmphosMRC03,
    bError => stErrors.bAmphosMRC03
);
*)

(*******************************************************************
Now that each of the input signals have been checked and errors stored,
run the interlock logic.
*******************************************************************)
(* Enable Amphos attenuator if interlock logic passes *)
(*
GVL_IO.bAmphosAttenuatorEnable := F_AmphosAttenuatorEnable(
    bAmphosOn := GVL_IO.bAmphosOn,
    stErrors := stErrors
);
Deprecated Logic -- Not Currently Used*)

fbLaserShutterInterlocks(
    fOpcpaVoltage                   := fbOpcpa.fVoltage,
    fAmphosVoltage                  := fbAmphos.fVoltage,
    fMPCVoltage                             := fbMPC.fVoltage,
    AmphosStatus                    := GVL_IO.bAmphosOn,
    bLoopTempOverride01     := fbChillerLoop01.bOperatorOverride,
    bLoopTempOverride02     := fbChillerLoop02.bOperatorOverride,
    bLoopTempOverride03     := fbChillerLoop03.bOperatorOverride,
    bLoopTempOverride04     := fbChillerLoop04.bOperatorOverride,
    stOpcpaSetpoints        := GVL_Laser.stOpcpaSP,
    stAmphosSetpoints               := GVL_Laser.stAmphosSP,
    stMpcSetpoints                  := GVL_Laser.stMpcSP,
    stErrors                := stErrors,
    Mode                                    := Mode_LPS,
    bAmphosShutter                  := GVL_IO.bAmphosShutterEnable,
    bCarbideShutter                 => GVL_IO.bCarbideShutterEnable
);

(*
(* Enable Amphos shutter if interlock logic passes *)
GVL_IO.bAmphosShutterEnable := F_AmphosShutterEnable(
    bAmphosOn           := GVL_IO.bAmphosOn,
    fOpcpaVoltage       := fbOpcpa.fVoltage,
    bLoopTempOverride01 := fbChillerLoop01.bOperatorOverride,
    bLoopTempOverride02 := fbChillerLoop02.bOperatorOverride,
    bLoopTempOverride03 := fbChillerLoop03.bOperatorOverride,
    bLoopTempOverride04 := fbChillerLoop04.bOperatorOverride,
    stOpcpaSetpoints    := GVL_Laser.stOpcpaSP,
    stErrors            := stErrors,
    bMasterOverride     := bMasterOverride,
    bAmpShutterClosed   := bAmpShutterClosed
);
*)

(*
(* Enable Carbide shutter if interlock logic passes *)
GVL_IO.bCarbideShutterEnable := F_CarbideShutterEnable(
    stErrors            := stErrors,
    bMasterOverride     := bMasterOverride
);
*)

(* Enable Dump chiller if interlock logic passes *)
GVL_IO.bDumpChillerRelay := F_DumpChillerEnable(
    stErrors            := stErrors,
    bMasterOverride     := bMasterOverride
);

(* Enable Baseplate chiller if interlock logic passes *)
GVL_IO.bBaseplateChillerRelay := F_BaseplateChillerEnable(
    stErrors        := stErrors,
    bMasterOverride := bMasterOverride
);

(* Enable Amphos if interlock logic passes *)
(*GVL_IO.bAmphosRelay := F_AmphosEnable(
    fAmphosVoltage      := fbAmphos.fVoltage,
    bEnabled            := GVL_IO.bAmphosRelay,
    stAmphosSetpoints   := GVL_Laser.stAmphosSP,
    stErrors            := stErrors,
    bMasterOverride     := bMasterOverride
);
*)

(*******************************************************************
Safety related tasks have now been finished. Now try slower tasks such as
communicating with external hardware.
*******************************************************************)
(* Read from TraceTek hardware to get leak details. Errors are
handled later when diagnostics are run *)
fbTraceTekModbus(
    nModbusSlaveAddress := GVL_TraceTek.nModbusSlaveAddress,
    bLeakDetected       := GVL_IO.bLeakDetected
);

(*******************************************************************
As per the specification, the reset PV is not checked until after the EPS has taken action.
Check the reset flags to see if they have been set and the errors can be unlatched.
*******************************************************************)
(* Check for rising-edge errors *)
fbErrorTriggers(stErrors := stErrors, nLeakLocations := fbLeakMonitor.nLeakLocations);

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

(* Check for new Amphos beam errors *)
IF fbErrorTriggers.stNewErrors.bAmphosBeam THEN
    bAmphosReset := FALSE; // If there is a new error turn off reset
ELSIF bAmphosReset THEN
    // If there are no new errors and reset is on, reset the errors
    fbAmphos.Reset();
    fbErrorTriggers.ResetAmphosTrigger();
END_IF

(* Check for new OPCPA errors *)
IF fbErrorTriggers.stNewErrors.bOpcpaBeam THEN
    bOpcpaReset := FALSE; // If there is a new error turn off reset
ELSIF bOpcpaReset THEN
    // If there are no new errors and reset is on, reset the errors
    fbOpcpa.Reset();
    fbErrorTriggers.ResetOpcpaTrigger();
END_IF

(* Check for new Beam Dump chiller errors *)
IF fbErrorTriggers.stNewErrors.bDumpChiller THEN
    bDumpChillerReset := FALSE; // If there is a new error turn off reset
ELSIF bDumpChillerReset THEN
    fbDumpChiller.Reset();
    fbErrorTriggers.ResetDumpChillerTrigger();
END_IF

(* Check for new Baseplate chiller errors *)
IF fbErrorTriggers.stNewErrors.bBaseplateChiller THEN
    bBaseplateChillerReset := FALSE; // If there is a new error turn off reset
ELSIF bBaseplateChillerReset THEN
    // If there are no new errors and reset is on, reset the errors
    fbBaseplateChiller.Reset();
    fbErrorTriggers.ResetBaseplateChillerTrigger();
END_IF

(* Remove Master Latch Reset after one scan *)
IF bMasterLatchReset AND bLatchStart THEN
    bMasterLatchReset := FALSE;
    bLatchStart := FALSE;
END_IF

END_PROGRAM
Related: