DUTs

E_PressureState

TYPE E_PressureState :
(
    // Invalid states
    PressInvalid,//  //gc_GaugeValidState - 1,
    GaugeDisconnected, //gc_GaugeValidState -2,
    OoR, //gc_GaugeValidState -6,
    // Ion gauges
    Off, //gc_GaugeValidState - 3,
    Starting, //gc_GaugeValidState - 4

    //Valid States (Positive)
    Valid, // gc_GaugeValidState set in "Global Variable Folder: Constants"
    ValidHi, //gc_GaugeValidState + 1,
    ValidLo //gc_GaugeValidState + 2,

) INT;
END_TYPE

E_PumpState

TYPE E_PumpState :
(
pumpSTOPPED := 0,
pumpSTARTING        := 1,
pumpRUNNING := 2,
pumpFAULT := 3,
pumpSTOPPING :=4
);
END_TYPE

E_ValvePositionState

TYPE E_ValvePositionState :
    (
OPEN        := 0,
CLOSED      := 1,
MOVING      := 2,
INVALID :=3,
OPEN_F :=4
);
END_TYPE

E_VCN

TYPE E_VCN :
    (
CloseValve  := 0,
OpenValve   := 1,
PressureControl     := 2,
ManualControl       := 3

    );
END_TYPE

E_VGC

TYPE E_VGC :
(

    Vented,
    AtVacuum,
    ERR_DiffPress,
    ERR_LostVac,
    ERR_ExtFault,
    At_Vac,
    Triggered,
    Vac_Fault,
    Cls_Timeout,
    Opn_Timeout


);
END_TYPE

Gauge_Type

TYPE Gauge_Type: (
PG722B := 0, //Baraton Gauge
IG903 := 1 , //Cold Cathode
PG907 := 2 , //Pirani Gauge
IG909:= 3 , //Hot Cathode
PG925 := 4 ); //MicroPirani Gauge
(* This enumeration should match up with the EDM MUX button on the vacuum gauge expert controls screen *)
END_TYPE

ST_AgilentPTM

TYPE ST_AgilentPTM EXTENDS ST_PTM :
STRUCT

(* Inputs *)
    (* 24V in Ramp state; 0V for others *)
    {attribute 'pytmc' := '
    pv: START;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xSTART :      BOOL;

    (* setpoint related to frequency *)
    {attribute 'pytmc' := '
    pv: R1Status;
    field: ZNAM FALSE;
    field: ONAM AT SPEED;
    io: i;
    '}
    i_xR1   :  BOOL;

    (* setpoint related to Power *)
    {attribute 'pytmc' := '
    pv: R2Status;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xR2   :  BOOL;

    (* low speed mode *)
    {attribute 'pytmc' := '
    pv: LSPD;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xLSpd :  BOOL;


    (* Fault input*)
    //i_xFault : BOOL;  Inherit ST_PTM.xFault



(* controls *)
    (* Start/Stop -
            q_RunDo ;   Inherit ST_PTM.
     *)

    (* Low speed control *)
    {attribute 'pytmc' := '
    pv: LSPD_DO;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    q_xLSpd         :  BOOL;

    (*Soft start *)
    {attribute 'pytmc' := '
    pv: XSS_DO;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    q_xSS           :  BOOL;

    (*pump Lock*)
    {attribute 'pytmc' := '
    pv: FaultLock;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    xPumpFaultLock : BOOL := FALSE;
    // i_xPumpLockReset: BOOL := FALSE; Inherit ST_PTM.xReset
    // Error messages
    {attribute 'pytmc' := '
    pv: ErrorMessage;
    io: i;
    '}
    sError : STRING;
    {attribute 'pytmc' := '
    pv: Power_MON;
    io: i;
    field: PREC 2
    field: EGU "W";
    '}
    i_rPowerMon : REAL;

END_STRUCT
END_TYPE

ST_EbaraDryPump

TYPE ST_EbaraDryPump EXTENDS ST_RoughPump :
STRUCT
(* Extension of the rough pump archetype for ebara
Applicable to:
EV-S20
EV-S50
EV-S100
EV-S200
*)

//Controls
{attribute 'pytmc' := '
    pv: MPStart;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
q_xMPStart  :       BOOL; //Main Pump start
{attribute 'pytmc' := '
    pv: BPStart;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
q_xBPStart  :       BOOL; // Booster Pump start (this can be started by the pump automatically)

xBPIlk      :       BOOL;   //Booster pump interlock
rBPIlkSP    :       REAL := 30; //Booster pump pressure setpoint
tonBP       :       TON := (PT:=T#5S); //Timer for pressure and valve stability


//Readbacks
{attribute 'pytmc' := '
    pv: MPStatus;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
i_xMPStatus :       BOOL; //MP status
{attribute 'pytmc' := '
    pv: BPStatus;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
i_xBPStatus :       BOOL; //BP status
{attribute 'pytmc' := '
    pv: WARN_DI;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
i_xWarning  :       BOOL; //Warning status
{attribute 'pytmc' := '
    pv: ALARM;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
i_xAlarm    :       BOOL; //Alarm, maps to error
{attribute 'pytmc' := '
    pv: REMOTE;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
i_xRemote   :       BOOL; //Remote control status


END_STRUCT
END_TYPE

ST_EbaraEVA

TYPE ST_EbaraEVA EXTENDS ST_RoughPump:
STRUCT

(* Output *)
    //q_xRunDO      :       BOOL; in Rough PUMP


(* Input *)
{attribute 'pytmc' := '
    pv: REMOTE;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: io;
    '}
    q_xRemote       :       BOOL;
    {attribute 'pytmc' := '
    pv: ALARM_OK;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
    i_xAlarmOK      :       BOOL;

    {attribute 'pytmc' := '
    pv: RUN_DI;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
    i_xIsRun                :       BOOL;



END_STRUCT
END_TYPE

ST_EbaraPTM

TYPE ST_EbaraPTM EXTENDS ST_PTM :
STRUCT
(* Extension of the PTM archetype for Ebara turbo controllers
Applicable to:
ETC series
*)
//Controls
    {attribute 'pytmc' := '
    pv: START;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
q_xStart   : BOOL; //Outputs - FB decides if these are actually set
    {attribute 'pytmc' := '
    pv: STOP;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
q_xStop    : BOOL;
    {attribute 'pytmc' := '
    pv: RESET;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
q_xReset   : BOOL; // xReset is momentary reset switch - resets protection functions
    {attribute 'pytmc' := '
    pv: PROT;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
q_xProtection : BOOL; //NC. If opened, pump will decelerate/not start
    {attribute 'pytmc' := '
    pv: SETSPEED
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: io
    '}
iq_xSpeedSet    : BOOL; //Request to set speed
    {attribute 'pytmc' := '
    pv: SPEED_REQ
    io: io
    field: EGU "Hz"
    '}
q_iSpeedSet   : DINT; //Requested speed (min. 100Hz)
    {attribute 'pytmc' := '
    pv: OVRD_ON
    field: ZNAM OFF;
    field: ONAM ON;
    io: io
    '}
i_xOverride   : BOOL; //Override mode - ignores interlocks

//Readbacks - also has Accel/At speed/Decel/Fault/Cur Speed (min 100RPM)
    {attribute 'pytmc' := '
    pv: ROTATE_STATUS
    field: ZNAM FALSE;
    field: ONAM ROTATING;
    io: i
    '}
i_xRotate : BOOL; //If open, pump is rotating
    {attribute 'pytmc' := '
    pv: DECEL
    field: ZNAM FALSE;
    field: ONAM DECELERATING;
    io: i
    '}
i_xDecel  : BOOL; //If closed, brake engaged (pump decelerating)
    {attribute 'pytmc' := '
    pv: FAULT_OK
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
i_xNCFault  : BOOL; //Normally closed fault wiring. If false, there is a fault, or cable is unplugged.



END_STRUCT
END_TYPE

ST_KashiyamaDryPump

TYPE ST_KashiyamaDryPump EXTENDS ST_RoughPump:
STRUCT

(* Output *)
    //q_xRunDO      :       BOOL; in Rough PUMP
    {attribute 'pytmc' := '
    pv: RESET_DO;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
    q_xResetDo      :       BOOL;
    {attribute 'pytmc' := '
    pv: LSPD_DO;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
    q_xLspdDo       :       BOOL;

(* Input *)
{attribute 'pytmc' := '
    pv: REMOTE;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
    i_xLocal        :       BOOL;
    {attribute 'pytmc' := '
    pv: ALARM_OK;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
    i_xAlarmOK      :       BOOL;
    {attribute 'pytmc' := '
    pv: WARN_OK;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
    i_xWarningOK    :       BOOL;
    {attribute 'pytmc' := '
    pv: RUN_DI;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
    i_xIsRun                :       BOOL;

(* Logic status *)

    xLspd_IlkENA:   BOOL;

(* EPICS *)
    //xRunSW                :       BOOL; in Rough PUMP
    {attribute 'pytmc' := '
    pv: RESET_SW;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: io
    '}
    xResetSW        :       BOOL; //For resetting faults
    {attribute 'pytmc' := '
    pv: LSPD_SW;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: io
    '}
    xLspdSW     :   BOOL;
    LowSP  :   REAL := 0.02; //Torr
    HighSP :   REAL; //Torr
    RdyTmr : REAL;
END_STRUCT
END_TYPE

ST_LeyboldPTM

TYPE ST_LeyboldPTM EXTENDS ST_PTM :
STRUCT
(* Extension of the PTM archetype for Oerlikon turbo controllers
Applicable to:
Mag Drive Digital
Mag Drive S
More?
*)
//Readbacks
    {attribute 'pytmc' := '
    pv: DECEL;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xDecel        :       BOOL;
    {attribute 'pytmc' := '
    pv: TEMP;
    field: EGU "C";
    io: i;
    '}
    i_diPumpTemp:   DINT;
    {attribute 'pytmc' := '
    pv: PWR;
    io: i;
    '}
    i_diPwr         :   DINT;
    i_diElecTemp:   DINT;
    {attribute 'pytmc' := '
    pv: OVR_TEMP;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xTempFault:   BOOL;
    i_xNCError      :       BOOL; //Using normally closed wiring

//Controls
    {attribute 'pytmc' := '
    pv: REMOTE;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: io;
    '}
    q_xRemote : BOOL; (* Remote control enabled *)
END_STRUCT
END_TYPE

ST_PfeifferPTM

TYPE ST_PfeifferPTM EXTENDS ST_PTM :
STRUCT
(* Extension of the PTM archetype for Pfeiffer turbos
Applicable to:
HiPace series w/ onboard controllers
More?
*)
    {attribute 'pytmc' := '
    pv: RESET;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    q_xReset   : BOOL;
    //Readbacks
    {attribute 'pytmc' := '
    pv: DrivePower_RBV;
    io: i;
    '}
    i_diPwr         :       DINT;
    {attribute 'pytmc' := '
    pv: TempElec_RBV;
    io: i;
    '}
    i_diElecTemp    :       DINT;
    {attribute 'pytmc' := '
    pv: TempPump_RBV;
    io: i;
    '}
    i_diBtmTemp:    DINT;
    {attribute 'pytmc' := '
    pv: TempBearing_RBV;
    io: i;
    '}
    i_diBrngTemp:   DINT;
    {attribute 'pytmc' := '
    pv: TempMotor_RBV;
    io: i;
    '}
    i_diMtrTemp:    DINT;
    {attribute 'pytmc' := '
    pv: ErrorCode_RBV;
    io: i;
    '}
    i_iErrorCode    :       INT; //might change these to enumeration someday
    {attribute 'pytmc' := '
    pv: WarningCode_RBV;
    io: i;
    '}
    i_iWarningCode  :       INT;
    {attribute 'pytmc' := '
    pv: TempFault_RBV;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xTempFault    :       BOOL;
    {attribute 'pytmc' := '
    pv: Power_RBV;
    io: i;
    '}
    i_uiPowerPctRbk :   UINT;


//Controls
    q_uiPowerPct : UINT := 100; //Should normally be 100

END_STRUCT
END_TYPE

ST_PIP

TYPE ST_PIP :
STRUCT
(* Read back *)
//  i_xHVisON : BOOL;

(* Interlock *)
    {attribute 'pytmc' := '
    pv: ILK_OK;
    field: ZNAM NOT OK ;
    field: ONAM OK ;
    io: i;
    '}
    xILKOk : BOOL;

    {attribute 'pytmc' := '
    pv: ERROR;
    field: ZNAM FALSE ;
    field: ONAM TRUE ;
    io: i;
    '}
    xError : BOOL;

    {attribute 'pytmc' := '
    pv: AT_VAC_SP;
    io: io;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rHVEna_SP : REAL := 1.0E-4;

    {attribute 'pytmc' := '
    pv: ILK_DEVICE;
    io: i;
    '}
    sIlkDeviceName: STRING;
    //Required for other devices using this gauge as interlock
    sPath: STRING;

(* EPICS Controls *)
    {attribute 'pytmc' := '
    pv: HV_SW;
    io: io;
    field: ZNAM OFF;
    field: ONAM ON;
    '}
    xHVEna_SW : BOOL;
    {attribute 'pytmc' := '
    pv: Auto_On;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io:i;
    '}
    xAutoOn : BOOL;

    {attribute 'pytmc' := '
    pv: OVRD_ON ;
    field: ZNAM Override OFF ;
    field: ONAM Override ON;
    io: io;
    '}
    xOverrideMode   :       BOOL; (* Shows the override status of this valve *)

    {attribute 'pytmc' := '
    pv: FORCE_START;
    io: io;
    field: ZNAM FALSE;
    field: ONAM FORCE START;
    '}
    pv_xOvrdStart   :       BOOL;


    {attribute 'pytmc' := '
    pv: SP_HYS;
    io: io;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    /// Protection setpoint hysteresis
    rHYS_PR: REAL := 0.001;
    {attribute 'pytmc' := '
    pv: AI_Offset;
    io: io;
    '}
    iOffset: INT:=13;
    {attribute 'pytmc' := '
    pv: Inverted;
    field: ZNAM NORMAL;
    field: ONAM INVERTED;
    io: io;
    '}
    bOutputInverted : BOOL;
(* IO Controls *)
    {attribute 'pytmc' := '
    pv: HV_DO;
    field: ZNAM OFF;
    field: ONAM ON;
    io: i;
    '}
    q_xHVEna_DO : BOOL;     // Enable High Voltage when True // 'TcLinkTo' (EL1124) ^Input
    {attribute 'pytmc' := '
    pv: PRESS;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    io: i;
    '}
    rPRESS: REAL;  //
    {attribute 'pytmc' := '
    pv: PRESS_AI;
    io: i;
    '}
    i_iPRESS: REAL;  //
    {attribute 'pytmc' := '
    pv: HV_DI;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io:i;
    '}
    i_xHV_DI : BOOL; // NO contact // 'TcLinkTo' (EL1004) ^Input

    {attribute 'pytmc' := '
    pv: STATE;
    field: ZRST STOPPED;
    field: ONST STARTING;
    field: TWST RUNNING;
    field: THST FAULT;
    field: FRST STOPPING;
    io: i;
    '}

    eState  :       E_PumpState;

    {attribute 'pytmc' := '
    pv: LOGGER;
    io: io;
    field: ZNAM OFF ;
    field: ONAM ON ;
    '}
    xLog : BOOL:=TRUE;
END_STRUCT
END_TYPE

ST_PTM

TYPE ST_PTM :
STRUCT
(* General PTM Structure
Each PTM might have a serial control structure, but all will have a general supervisory control structure

NOTE: This is an archetype, use an extension of this structure for a specific pump (or make one).

*)

(* Controls *)
    {attribute 'pytmc' := '
    pv: RUN_SW;
    field: ZNAM STOP;
    field: ONAM RUN;
    io: io;
    '}
    xRunSW  :       BOOL;
    {attribute 'pytmc' := '
    pv: RST_SW;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: io;
    '}
    xResetSW        :       BOOL; //For resetting faults
    {attribute 'pytmc' := '
    pv: RUN_DO;
    io: i;
    '}
    q_RunDO         :       BOOL;

(* Readbacks *)
    {attribute 'pytmc' := '
    pv: ILK_OK;
    field: ZNAM ILK ACTIVE;
    field: ONAM ILK OK;
    io: i;
    '}
    xExtRunOk       :       BOOL; (* also a control *)
    {attribute 'pytmc' := '
    pv: ACCEL;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xAccel        :       BOOL;
    {attribute 'pytmc' := '
    pv: AT_SPD;
    field: ZNAM FALSE;
    field: ONAM AT SPEED;
    io: i;
    '}
    i_xAtSpd        :       BOOL;
    {attribute 'pytmc' := '
    pv: SPEED;
    io: i;
    field: EGU "Hz"
    '}
    i_diCurSpd      :       DINT;

    xCommTimeout    :       BOOL := TRUE;  (* Initialized true since we haven't talked to pump yet *)
(* Alarm Outputs *)
    {attribute 'pytmc' := '
    pv: FAULT;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xFault                :       BOOL; (*FAULT*)
    {attribute 'pytmc' := '
    pv: WARN;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xWarn         :       BOOL; (* warning *)
    {attribute 'pytmc' := '
    pv: ALARM;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    i_xALARM        :       BOOL; (* ALARM *)

(* IWS instructions *)
    xActive :       BOOL; // this bit is set to add the pump to the system
    iPumpGrp        :       INT; // all members of a pump group start at the same time

(* Operational Setpoints *)
    rForelineSP     :       REAL := 0.5;

    {attribute 'pytmc' := '
    pv: BP_SP;
    io: io;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rBackingPressureSP : REAL := 0.01;
    {attribute 'pytmc' := '
    pv: IP_SP;
    io: io;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rInletPressureSP : REAL := 0.02; //20mTorr
(*State*)
    {attribute 'pytmc' := '
    pv: STATE;
    field: ZRST STOPPED;
    field: ONST STARTING;
    field: TWST RUNNING;
    field: THST FAULT;
    field: FRST STOPPING;
    io: i;
    '}

    eState  :       E_PumpState;

    {attribute 'pytmc' := '
    pv: CURR_MON;
    io: i;
    field: PREC 2
    field: EGU "A";
    '}
    i_rCurrentMon : REAL;

    {attribute 'pytmc' := '
    pv: TEMP_MON;
    io: i;
    field: PREC 2;
    field: EGU "C";
    '}
    i_rTempMon      :       REAL;

    {attribute 'pytmc' := '
    pv: LOGGER;
    io: i;
    field: ZNAM OFF ;
    field: ONAM ON ;
    '}
    xLog : BOOL:=TRUE;


END_STRUCT
END_TYPE

ST_RoughPump

TYPE ST_RoughPump :
STRUCT

(* Generic roughing pump controls

NOTE: This is an archetype, use an extension of this structure for a specific pump (or make one).

*)

//Controls
{attribute 'pytmc' := '
    pv: RUN_SW;
    field: ZNAM Stop;
    field: ONAM Start;
    io: io;
    '}
pv_xRunSW   :       BOOL; //epics/ software control switch
{attribute 'pytmc' := '
    pv: RUN_DO;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
q_xRunDo    :   BOOL;

//Status and Readback
{attribute 'pytmc' := '
    pv: ILK_OK;
    field: ZNAM NOT OK ;
    field: ONAM OK ;
    io: i
    '}
xIlkOK      :       BOOL; //Interlock bit, true means OK to run
xExtIlk     :       BOOL; //External interlock, this is where the logic goes
{attribute 'pytmc' := '
    pv: AT_SPD;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
xAtSpd      :       BOOL; //Pump at speed setpoint
{attribute 'pytmc' := '
    pv: WARN;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
xWrn        :       BOOL; //Pump warning
iWrn        :       BOOL; //Warning state/code
{attribute 'pytmc' := '
    pv: ERROR;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i
    '}
xErr        :       BOOL; //Error summary
iErr        :       BOOL; //Error state/code

(*State*)
    {attribute 'pytmc' := '
    pv: STATE;
    field: ZRST STOPPED;
    field: ONST STARTING;
    field: TWST RUNNING;
    field: THST FAULT;
    field: FRST STOPPING;
    io: i;
    '}

    eState  :       E_PumpState;

END_STRUCT
END_TYPE

ST_ValveBase

TYPE ST_ValveBase :
STRUCT
    (* EPICS Controls *)
    {attribute 'pytmc' := '
    pv: OPN_SW;
    field: ZNAM CLOSE;
    field: ONAM OPEN;
    io: io ;
    '}
    pv_xOPN_SW      : BOOL;
    {attribute 'pytmc' := '
    pv: ALM_RST;
    io: io;
    '}
    pv_xAlmRst      : BOOL;
    {attribute 'pytmc' := '
    pv: FORCE_OPN;
    io: io;
    field: ZNAM FALSE;
    field: ONAM FORCE OPEN;
    '}
    pv_xOvrdOpn     :       BOOL;

    {attribute 'pytmc' := '
    pv: OVRD_ON ;
    field: ZNAM Override OFF ;
    field: ONAM Override ON;
    io: io;
    '}
    xOverrideMode   :       BOOL; (* Shows the override status of this valve *)

(* I/Os*)
(* Readbacks *)
    {attribute 'pytmc' := '
    pv: OPN_DI;
    io: i;
    field: ZNAM FALSE;
    field: ONAM OPEN;
    '}
    i_xOpnLS        : BOOL;
    {attribute 'pytmc' := '
    pv: CLS_DI;
    io: i;
    field: ZNAM FALSE;
    field: ONAM CLOSE;
    '}
    i_xClsLS        : BOOL;
(* Controls *)
    {attribute 'pytmc' := '
    pv: OPN_DO;
    io: i;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    '}
    q_xOPN_DO       : BOOL;
(* Interlocks *)
    {attribute 'pytmc' := '
    pv: EXT_ILK_OK ;
    field: ZNAM NOT OK ;
    field: ONAM OK ;
    io: i ;
    '}
    xEXT_OK :       BOOL := FALSE;  (* External interlock for custom interlocking in addition to regular DP ilk, this must be set true, or the interlock condition before calling the FB_VGC *)


    {attribute 'pytmc' := '
    pv: OPN_OK;
    field: ZNAM OPN ILK NOT OK ;
    field: ONAM OPN ILK OK ;
    io: i;
    '}
    xOPN_OK :       BOOL;   (* Final SUM of DP_OK and EXT_OK, needed because it allows the DP ilk to be switched off, see FB_VGC.Dis_DPIlk *)

(* States *)
{attribute 'pytmc' := '
    pv: POS_STATE;
    type: mbbi ;
    field: ZRST OPEN ;
    field: ONST CLOSED ;
    field: TWST MOVING ;
    field: THST INVALID ;
    field: FRST OPEN_F ;
    io: i;
    '}
    eState  :       E_ValvePositionState := INVALID;

    {attribute 'pytmc' := '
    pv: STATE;
    field: ZRST Vented;
    field: ONST At Vacuum;
    field: TWST Differential Pressure;
    field: THST Lost Vacuum;
    field: FRST Ext Fault;
    field: FVST AT Vacuum;
    field: SXST Triggered;
    field: SVST Vacuum Fault;
    field: EIST Close Timeout;
    field: NIST Open Timeout;
    io: i;
    '}
    eVGC_State : E_VGC;

(* Error *)

    {attribute 'pytmc' := '
    pv: ERROR;
    field: ZNAM NO ERROR ;
    field: ONAM ERROR PRESENT ;
    io: o;
    '}
    bErrorPresent : BOOL;
    iErrorCode: INT;
    {attribute 'pytmc' := '
    pv: ErrMsg;
    io: o;
    '}
    sErrorMessage: STRING;
    {attribute 'pytmc' := '
    pv: LOGGER;
    io: io;
    field: ZNAM OFF ;
    field: ONAM ON ;
    '}
    xLog : BOOL:=TRUE;

END_STRUCT
END_TYPE

ST_VCC_NO

TYPE ST_VCC_NO:
STRUCT

(* A normally open valve needs permission to close! *)
(* Inputs *)
    {attribute 'pytmc' := '
    pv: CLS_SW ;
    field: ONAM CLOSE;
    field: ZNAM OPEN;
    io: io ;
    '}
    pv_xCLS_SW      : BOOL;
    {attribute 'pytmc' := '
    pv: FORCE_CLS;
    field: ZNAM FALSE;
    field: ONAM FORCE CLOSE;
    io: io;
    '}
    pv_xOvrdCls     :       BOOL;
    {attribute 'pytmc' := '
    pv: CLS_OK;
    field: ZNAM FALSE;
    field: ONAM CLS_OK;
    io: i;
    '}
    xCLS_OK : BOOL;

    {attribute 'pytmc' := '
    pv: OVRD_ON;
    field: ZNAM Override OFF ;
    field: ONAM Override ON;
    io: io
    '}
    xOverrideMode   :       BOOL; (* Shows the override status of this valve *)

(* Outputs *)
    {attribute 'pytmc' := '
    pv: CLS_DO;
            field: ZNAM FALSE;
    field: ONAM TRUE;
    io: i;
    '}
    xCLS_DO : BOOL;

END_STRUCT
END_TYPE

ST_VCN

TYPE ST_VCN :
STRUCT
    (* VCN - Valve Controlled Needle
    Used for Pfeiffer EVR 116 needle valves *)
    {attribute 'pytmc' := '
    pv: OPN_SW;
    field: ZNAM CLOSE;
    field: ONAM OPEN;
    io: io ;
    '}
    xOPN_SW : BOOL;
    {attribute 'pytmc' := '
    pv: POS_RDBK;
    io: i;
    '}
    // Inputs
    i_iPosition     :       REAL; //Position readback (if it exists)

    // Outputs
    {attribute 'pytmc' := '
    pv: POS_AO_R;
    io: i ;
    '}
    q_iRawPosition  :       INT; //Position control
    //For the EVR 116 this is 0-10V analog 0.4 closed to 9.1 Closed

    //Softvariables
    {attribute 'pytmc' := '
    pv: ILK_OK;
    field: ZNAM NOT OK ;
    field: ONAM OK ;
    io: i;
    '}
    xIlkOK  :       BOOL := FALSE; // Interlock Bit

    rUpperLimit     :       REAL:=100; // Percentage9.1;    //Upper limit on valve open
    {attribute 'pytmc' := '
    pv: POS_REQ;
    io: io;
    '}
    rReqPosition    :       REAL; //Requested position (0.0-100.0%)
    {attribute 'pytmc' :=  '
    pv: STATE ;
    field: ZRST Close ;
    field: ONST Open;
    field: TWST PressureControl ;
    field: THST ManualControl ;
    io: io
    '}
    eValveControl   :       E_VCN := CloseValve; // Valve control state

    ftIlk   :       F_TRIG;

END_STRUCT
END_TYPE

ST_VFS

TYPE ST_VFS EXTENDS ST_ValveBase:
STRUCT

(* Interlock *)
    {attribute 'pytmc' := '
    pv: TRIG;
    field: ZNAM TRIG_OFF;
    field: ONAM TRIG_ON;
    io: i ;
    '}
    i_xTrigger : BOOL;
(* Commands *)
    {attribute 'pytmc' := '
    pv: CLS_SW;
    field: ZNAM FALSE;
    field: ONAM CLOSE;
    io: io ;
    '}
    xCLS_SW AT%Q*:BOOL; (*external open signal e.g epics*)

(* Alarm Outputs *)
    {attribute 'pytmc' := '
    pv: ERR_Ext;
    field: ZNAM NO ERROR ;
    field: ONAM External error present ;
    io: i;
    '}
    xERR_ExtFault           :       BOOL;
    {attribute 'pytmc' := '
    pv: VAC_FAULT_OK;
    field: ZNAM FAULT ;
    field: ONAM FAULT OK;
    io: i ;
    '}
    i_xVAC_FAULT_OK : BOOL; (*Valve Vacuum OK, is set to False when there is Vacuum Fault*)


(*ILK Devices*)
    {attribute 'pytmc' := '
    pv: GFS;
    io: i;
    '}
    sGFS: String;

END_STRUCT
END_TYPE

ST_VG

TYPE ST_VG :
STRUCT
    /// A general gauge structure is used to make the rest of the interlocking simpler. There are some parameters for cold cathodes that are not used by pirani.
    ///These features aren't disabled, they just aren't used, think child/parent classes.
    ///
    {attribute 'pytmc' := '
    pv: PRESS;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    io: i;
    '}
    rPRESS: REAL;  //This is the human-readable pressure
    {attribute 'pytmc' := '
    pv: AT_VAC;
    io: i;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    '}
    xAT_VAC: BOOL;
    {attribute 'pytmc' := '
    pv: PRESS_OK;
    field: ZNAM OFF;
    field: ONAM ON;
    io: i;
    '}
    xPRESS_OK: BOOL;
    {attribute 'pytmc' := '
    pv: STATE;
    field: ZRST PressInvalid;
    field: ONST GaugeDisconnected;
    field: TWST OoR;
    field: THST Off;
    field: FRST Starting;
    field: FVST Valid;
    field: SXST ValidHi;
    field: SVST ValidLo;
    io: i;
    '}
    eState  :       E_PressureState;
    (* EPICS Controls *)
    {attribute 'pytmc' := '
    pv: HV_SW;
    io: io;
    field: ZNAM OFF;
    field: ONAM ON;
    '}
    xHV_SW: BOOL; // High Voltage Switch from epics

    /// Controls and I/Os
    {attribute 'pytmc' := '
    pv: PRESS_AI;
    io: i;
    '}
    i_iPRESS_R  :INT; // input Pressure // Link to analog Input
    {attribute 'pytmc' := '
    pv: HV_ON;
    io: i;
    '}
    i_xHV_ON        : BOOL; //  True when High Voltage is on
    {attribute 'pytmc' := '
    pv: DISC_ACTIVE;
    field: ZNAM NO DISC;
    field: ONAM DISC ACTIVE;
    io: i;
    '}
    i_xDisc_Active : BOOL;// Discharge Current Active
    {attribute 'pytmc' := '
    pv: HV_DIS_DO;
    io: i;
    field: ZNAM FALSE ;
    field: ONAM TRUE ;
    '}
    q_xHV_DIS : BOOL; // Enable High Voltage when True
    //
    wHV_RO: WORD;

    /// Bakeout bit
    xBAKEOUT: BOOL;

    //enum for gauge type - will replace iType
    eTYPE: Gauge_Type := PG907;
    //Gauge type, deprecated (default to pirani)
    iTYPE: INT := 2;
    // Index location of the associated Pirani Gauge
    wPG: WORD;


    xTurnOnTime: BOOL;/// Turn on Timers for cold cathode warmup

    iVacSp  :       INT;/// At vacuum setpoint for all gauges
    {attribute 'pytmc' := '
    pv: VAC_SP;
    io:io;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rVAC_SP: REAL := 0.001; /// At vacuum setpoint for all gauges
    /// Protection setpoint for ion gauges at which the gauge turns off, not used for pirani
    {attribute 'pytmc' := '
    pv: PRO_SP;
    io: io;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rPRO_SP: REAL := 0.001;
    {attribute 'pytmc' := '
    pv: SP_HYS;
    io: io;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    /// Protection setpoint hysteresis
    rHYS_PR: REAL := 0.001;
    {attribute 'pytmc' := '
    pv: ILK_OK;
    field: ZNAM NOT OK ;
    field: ONAM OK ;
    io: i;
    '}
    xILKOk  :       BOOL; (* also a control *)
    {attribute 'pytmc' := '
    pv: LOGGER;
    io: io;
    field: ZNAM OFF ;
    field: ONAM ON ;
    '}
    xLog : BOOL:=TRUE;
    //Required for other devices using this gauge as interlock
    sPath: STRING;

    /// Full scale pressure in Torr for baratron pressure conversion
    rFULL_SCALE: REAL := 1000;

END_STRUCT
END_TYPE

ST_VGC

TYPE ST_VGC EXTENDS ST_ValveBase:
STRUCT


(* Interlocks *)

    {attribute 'pytmc' := '
    pv: DP_OK;
    field: ZNAM DP NOT OK ;
    field: ONAM DP OK ;
    io: i;
    '}
    xDP_OK  :       BOOL;   (* Managed by the VGC function (FB_VGC) *)// Indicates the valve can be opened because the differential pressure is low enough

    {attribute 'pytmc' := '
    pv: AT_VAC_SP;
    io: o;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rAT_VAC_SP      :       REAL := 0.000001;       // Interlock setpoint for gauges on both sides of valve
    rAT_VAC_SP_LAST :       REAL :=0.000001 ;       // Interlock setpoint for gauges on both sides of valve
    {attribute 'pytmc' := '
    pv: AT_VAC_HYS;
    io: o;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rAT_VAC_HYS     :       REAL :=0.000001;        // Hysteresis of the vacuum sp
    {attribute 'pytmc' := '
    pv: HYST_PERC ;
    io: o;
    autosave_pass1: VAL DESC
    '}
    rHYST_PERC      :       REAL := 0.80;   // Hysteresis percentage

    {attribute 'pytmc' := '
    pv: AT_VAC ;
    field: ZNAM NOT AT VAC ;
    field: ONAM AT VAC ;
    io: i;
    '}
    xAT_VAC :       BOOL;   //At vacuum setpoint


(* Alarm Outputs *)
    {attribute 'pytmc' := '
    pv: ERR_DifPres;
    field: ZNAM NO ERROR ;
    field: ONAM Diffrential error present ;
    io: i;
    '}
    xERR_DifPres            :       BOOL;
    {attribute 'pytmc' := '
    pv: ERR_SP;
    field: ZNAM NO ERROR ;
    field: ONAM Setpoint error present ;
    io: i;
    '}
    xERR_SP                 :       BOOL;
    {attribute 'pytmc' := '
    pv: ERR_Ext;
    field: ZNAM NO ERROR ;
    field: ONAM External error present ;
    io: i;
    '}
    xERR_ExtFault           :       BOOL;
    xAlmSum                 :       BOOL;

(*ILK Devices*)
{attribute 'pytmc' := '
    pv: ILK_DEVICE_US;
    io: i;
    '}
    sIlkUSDeviceName: STRING;
    {attribute 'pytmc' := '
    pv: ILK_DEVICE_DS;
    io: i;
    '}
    sIlkDSDeviceName: STRING;

END_STRUCT
END_TYPE

ST_VRC

TYPE ST_VRC EXTENDS ST_ValveBase:
STRUCT
(* Readbacks *)
    //In case VRC is normally open
    {attribute 'pytmc' := '
    pv: CLS_OK;
    io: i;
    '}
    xCLS_OK : BOOL := TRUE;

END_STRUCT
END_TYPE

ST_VVC

TYPE ST_VVC :
STRUCT

(* Inputs *)

    {attribute 'pytmc' := '
    pv: OPN_SW;
    field: ZNAM CLOSE;
    field: ONAM OPEN;
    io: io;
    '}
    pv_xOPN_SW      : BOOL;
    {attribute 'pytmc' := '
    pv:FORCE_OPN;
    field: ZNAM FALSE;
    field: ONAM OPEN;
    io: io;
    '}
    xOvrdOpn        :       BOOL;
    {attribute 'pytmc' := '
    pv: OVRD_ON;
    field: ZNAM Override ON ;
    field: ONAM Override OFF;
    io: io;
    '}
    xOverrideMode   :       BOOL; (* Shows the override status of this valve *)
    {attribute 'pytmc' := '
    pv: OPN_OK;
    field: ZNAM OPN ILK NOT OK ;
    field: ONAM OPN ILK OK ;
    io: i;
    '}
    xOPN_OK : BOOL;

(* Outputs *)
    {attribute 'pytmc' := '
    pv: OPN_DO;
    field: ZNAM CLOSE;
    field: ONAM OPEN;
    io: i;
    '}
    q_xOPN_DO       : BOOL;

END_STRUCT
END_TYPE

GVLs

Constants

VAR_GLOBAL CONSTANT
    gc_iSizeOfGGOArray : INT := 50;
    gc_GaugeValidState :    INT := 4;
END_VAR

Global_Variables

VAR_GLOBAL
    g_iSizeOfGGOArray : INT := 50;

    g_stSystem      :       ST_System(*System_Struct*):= (
            xFirstScan := TRUE
    );

    g_DummyVG       :       ST_VG;


     fbGetCurTaskIdx : GETCURTASKINDEX;



END_VAR

Global_Version

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

POUs

F_RoughPumpInlet_ILK

(*
    Determine whether a pump attached to the inlet of a roughing pump is safe to open.

    Return true, signaling it is safe to open when the roughing pump is confirmed to be working.
*)

FUNCTION F_RoughPumpInlet_ILK : BOOL
VAR_INPUT
    ScrollPump      :       ST_RoughPump;
END_VAR
VAR
END_VAR
F_RoughPumpInlet_ILK := (ScrollPump.eState = pumpRUNNING); (* Confirm that the roughing pump is running before opening the valve *)

END_FUNCTION

F_TURBO_VRC_ILK

(* Close the vic if the roughing pump pirani loses its at-vac setpoint while the turbo is pumping *)
FUNCTION F_TURBO_VRC_ILK : BOOL
VAR_INPUT

    i_xHiVac        : BOOL;
    i_xRunPTM       : BOOL;
    i_xFLVac        : BOOL;

END_VAR
VAR

    rHysterisis : REAL := 0;

    xInterlock: BOOL;
    rTolerance: REAL := 20;
END_VAR
(* Close the vic if the roughing pump pirani loses its at-vac setpoint while the turbo is pumping *)
F_TURBO_VRC_ILK := (NOT i_xHiVac AND NOT i_xRunPTM AND NOT i_xFLVac) OR
                                    (NOT i_xRunPTM AND i_xFLVac) OR
                                    (i_xRunPTM AND i_xFLVac);

END_FUNCTION

F_TurboExtILK_NO

(* This function Block interfaces with a NO turbo valve*)
(* Mainly used in the FEE HXR GATT*)
FUNCTION F_TurboExtILK_NO : BOOL
VAR_INPUT
    Turbo           :       ST_PTM;
    BackingGauge:   ST_VG;
    InletGauge      :       ST_VG;
    VentValve       :       ST_VCC_NO; //NO type turbo valve
    ScrollPump      :       ST_RoughPump;
END_VAR

VAR
END_VAR


END_FUNCTION

F_TurboExtILK_NO_1

(* This function Block interfaces with a NO turbo valve*)
(* Mainly used in the FEE HXR GATT*)
FUNCTION F_TurboExtILK_NO_1 : BOOL
VAR_INPUT
    Turbo           :       ST_PTM;
    BackingGauge:   ST_VG;
    InletGauge      :       ST_VG;
    VentValve       :       ST_VCC_NO; //NO type turbo valve
    ScrollPump      :       ST_RoughPump;
END_VAR


END_FUNCTION

F_TurboExtILKLogic

(* This function evaluates the Turbo ILK logic*)
(* The Turbo shall not Run if the vent valve is open*)
(* The function also switches off the Turbo if the Inlet pressure or the backing Pressure*)
(* are higher than the configured set point*)
FUNCTION F_TurboExtILKLogic : BOOL
VAR_INPUT
    Turbo           :       ST_PTM;
    BackingGauge:   ST_VG;
    InletGauge      :       ST_VG;
    VentValve       :       ST_VVC;
    ScrollPump      :       ST_RoughPump;
END_VAR

VAR
END_VAR


END_FUNCTION

F_TurboExtILKLogic_2

(* This function evaluates the Turbo ILK logic*)
(* The function switches off the Turbo if the Inlet pressure or the backing Pressure*)
(* are higher than the configured set point*)
FUNCTION F_TurboExtILKLogic_2 : BOOL
VAR_INPUT
    Turbo           :       ST_PTM;
    BackingGauge:   ST_VG;
    InletGauge      :       ST_VG;
    ScrollPump      :       ST_RoughPump;
END_VAR
VAR
END_VAR


END_FUNCTION

F_TurboGateValve_ILK

(* This Function Block evaluates the ILK condition of the Turbo Gate valve *)
(* The logic Protects the Turbo pump from inlet pressure above the SP*)

FUNCTION F_TurboGateValve_ILK : BOOL (* function return TRUE when ILK is OK*)
VAR_INPUT
    i_Turbo : ST_PTM; // Turbo Pump
    i_stISG : ST_VG; //Gauge measuring inlet Pressure e.g Pirani
END_VAR
VAR
END_VAR
(* This Function Block evaluates the ILK condition of the Turbo Gate valve *)
(* The logic Protects the Turbo pump from inlet pressure above the SP*)
F_TurboGateValve_ILK := (NOT (i_Turbo.eState = pumpSTOPPED) AND NOT (i_Turbo.eState = pumpSTOPPING) AND NOT (i_Turbo.eState = pumpFAULT))
                                            AND (i_stISG.xPRESS_OK AND i_stISG.rPRESS < i_Turbo.rInletPressureSP);

END_FUNCTION

F_TurboGateValve_ILK_1

(* This Function Block evaluates the ILK condition of the Turbo Gate valve *)
(* The logic Protects the Turbo pump from inlet pressure above the SP*)

FUNCTION F_TurboGateValve_ILK_1 : BOOL (* function return TRUE when ILK is OK*)
VAR_INPUT
    i_Turbo : ST_PTM; // Turbo Pump
    i_stISG : ST_VG; //Gauge measuring inlet Pressure e.g Pirani
END_VAR
VAR
END_VAR
(* This Function Block evaluates the ILK condition of the Turbo Gate valve *)
(* The logic Protects the Turbo pump from inlet pressure above the SP*)
F_TurboGateValve_ILK_1 := (NOT (i_Turbo.eState = pumpSTOPPED) AND NOT (i_Turbo.eState = pumpFAULT))
                                            AND (i_stISG.xPRESS_OK AND i_stISG.rPRESS < i_Turbo.rInletPressureSP);

END_FUNCTION

F_TurboGateValve_Protection_ILK

(* This Function Block evaluates the ILK condition of the Turbo Gate valve *)
(* The logic Protects the Turbo pump from inlet pressure above the SP*)
(* And Protects the Turbo pump from backing pressure above the SP*)
(* And Protects the Turbo pump in the case of backing pump not running*)

FUNCTION F_TurboGateValve_Protection_ILK : BOOL (* function return TRUE when ILK is OK*)
VAR_INPUT
    i_Turbo : ST_PTM; // Turbo Pump
    i_stISG : ST_VG; //Gauge measuring inlet Pressure e.g Pirani
    i_stBSG : ST_VG; //Gauge measuring backing Pressure e.g Pirani
    ScrollPump      :       ST_RoughPump; // Roughing pump
END_VAR
VAR
END_VAR
(* This Function Block evaluates the ILK condition of the Turbo Gate valve *)
(* The logic Protects the Turbo pump from inlet pressure above the SP*)
F_TurboGateValve_Protection_ILK := (NOT (i_Turbo.eState = pumpSTOPPED) AND NOT (i_Turbo.eState = pumpSTOPPING) AND NOT (i_Turbo.eState = pumpFAULT))
                                            AND (i_stISG.xPRESS_OK AND i_stISG.rPRESS < i_Turbo.rInletPressureSP)
                                            AND (i_stBSG.xPRESS_OK AND i_stBSG.rPRESS < i_Turbo.rBackingPressureSP)
                                            AND (ScrollPump.eState = pumpRUNNING);

END_FUNCTION

F_VALVE_REL_ILK

(* OK, this is going to be a general-use function that will return whether the ratio of the pressures given is less than x orders of magnitude.
True iff 10^(-x) < a/b < 10^x.  Simple, right?  Just make sure it gets a positive order of magnitude and the pressures aren't 0. ~Scott *)
{attribute 'no_check'}
FUNCTION F_VALVE_REL_ILK : BOOL
VAR_INPUT
    i_stUSG :       ST_VG; // Upstream Gague
    i_stDSG :       ST_VG; // Downstream Gague
    //Orders of magnitude allowed between up/downstream pressures for valve to open. Must be real positive integer.
    i_iOrdersMagnitude: INT;
END_VAR
VAR
    rPressRatio : REAL;
    rMaxRatioAllowed        :       LREAL := EXPT(10,i_iOrdersMagnitude);
    rMinRatioAllowed        :       LREAL := EXPT(10,(-i_iOrdersMagnitude));
END_VAR
(* S. Stubbs 11/2015 *)

(* OK, this is going to be a general-use function that will return whether the ratio of the pressures given is less than x orders of magnitude.
True iff 10^(-x) < a/b < 10^x.  Simple, right?  Just make sure it gets a positive order of magnitude and the pressures aren't 0. ~Scott *)

(* Find ratio *)

IF i_stDSG.rPRESS <> 0 THEN
    rPressRatio := i_stUSG.rPRESS / i_stDSG.rPRESS;

    F_VALVE_REL_ILK := rPressRatio < rMaxRatioAllowed AND rMinRatioAllowed < rPressRatio;
ELSE
    F_VALVE_REL_ILK := FALSE;
END_IF

END_FUNCTION

F_VRC_DIODE_ILK

(* The principle is simple, a VRC should never open when B side pressure is greater than
the A side pressure. *)

FUNCTION F_VRC_DIODE_ILK : BOOL
VAR_INPUT

    i_stPGA : ST_VG; // A side pressure gauge
    i_stPGB : ST_VG; // B side pressure gauge

END_VAR
VAR

    rHysterisis : REAL := 0;

    xInterlock: BOOL;
    rTolerance: REAL := 20;
END_VAR
(* The principle is simple, a VRC should never open when B side pressure is greater than the A side pressure. *)
rTolerance := 20;
F_VRC_DIODE_ILK := ((i_stPGA.rPRESS + rTolerance >= i_stPGB.rPRESS + rHysterisis) OR i_stPGA.xBAKEOUT) AND i_stPGA.xPRESS_OK AND i_stPGB.xPRESS_OK;

(* OK it gets a bit more complicated with hysterisis,

Now, if the F_VIC_DIODE_ILK is FALSE then some hysterisis is added to the B side. But if it's OK then we just stick with making sure that the A side is greater than the B side

*)
(*
IF NOT F_VIC_DIODE_ILK THEN
Hysterisis := 40;
ELSE
Hysterisis := 0;
END_IF
*)

END_FUNCTION

F_VRC_DIODE_ILK_OK

(*The function returns TRUE i.e. ILK_OK when pressure on A side is greater than or equal to the pressure on B side*)
FUNCTION F_VRC_DIODE_ILK_OK : BOOL
VAR_INPUT

    i_stPGA : ST_VG; // A side pressure gauge
    i_stPGB : ST_VG; // B side pressure gauge

END_VAR
VAR
    rHysterisis : REAL := 0;

    xInterlock: BOOL;
    rTolerance: REAL := 0;
END_VAR
(* The principle is simple, a VRC should never open when B side pressure is greater than the A side pressure. *)
F_VRC_DIODE_ILK_OK := (i_stPGA.rPRESS + rTolerance >= i_stPGB.rPRESS + rHysterisis) AND i_stPGA.xPRESS_OK AND i_stPGB.xPRESS_OK;

END_FUNCTION

FB_972

{attribute 'no_check'}
FUNCTION_BLOCK FB_972  EXTENDS FB_GaugeBase
VAR_INPUT
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    PG      : ST_VG;
END_VAR
VAR_IN_OUT

END_VAR
VAR
    rV : REAL;
    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR
(* Standard MKS 9XX series conversion *)
(* works for 972 and 925 *)
// no div by zero
If (iTermBits=0) THEN iTermBits := 32767;END_IF
rV := 10*INT_TO_REAL(PG.i_iPRESS_R)/iTermBits;

IF rV > 1 THEN
    PG.rPRESS := LREAL_TO_REAL(EXPT(10, 2*rV-11));
    PG.xPRESS_OK := TRUE;
    PG.eState := Valid; //Press OK
ELSE
    PG.xPRESS_OK := FALSE;
    PG.eState := OoR;
END_IF



(* Soft IO Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
PG.i_iPRESS_R :=i_iPRESS_R;
END_ACTION

FB_9XX

(* Standard MKS 9XX series conversion *)
(* works for 925 *)
FUNCTION_BLOCK FB_9XX EXTENDS FB_GaugeBase
VAR_INPUT
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    PG      : ST_VG;
END_VAR
VAR_IN_OUT
END_VAR
VAR
    rV : REAL;
    fbGaugeState : FB_PressureState;
    rMaxPressure    :       REAL := 760; //Torr
    rMinPressure    :       REAL := 1E-5; //Torr
    (*Default set point 50 mT*)
    rVAC_SP: REAL := 5E-2;
    iTermBits: UINT := 30518 ; // The terminal's maximum value in bits default el3174 as per vacuum architecture
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR
(* Standard MKS 9XX series conversion *)
(* works for 972 and 925 *)

//Default setpoint 50 mT
IF PG.rVAC_SP = 0 THEN
    PG.rVAC_SP := rVAC_SP;
END_IF
// check no div by zero
If (iTermBits=0) THEN iTermBits := 30518;END_IF
rV := 10*INT_TO_REAL(PG.i_iPRESS_R)/iTermBits;

IF rV >= 0.99 THEN
    IF rV >= 0.99 AND rV <= 1 THEN
            PG.rPRESS := 1E-5;
    ELSE
            PG.rPRESS := LREAL_TO_REAL(EXPT(10, rV-6));
    END_IF
    PG.xPRESS_OK := TRUE;
    PG.eState := Valid;
ELSE
    PG.xPRESS_OK := FALSE;
    IF (rV = 0) THEN PG.eState := GaugeDisconnected;
            ELSE  PG.eState := PressInvalid;
    END_IF
END_IF

(* Setpoint evaluation *)
PG.xAT_VAC := PG.xPRESS_OK AND (PG.rPRESS < PG.rVAC_SP);


(*Logger*)
ACT_Logger();
(*Soft IO Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
PG.i_iPRESS_R :=i_iPRESS_R;
END_ACTION
ACTION ACT_Logger:
//STATE Logger
IF ePrevState <> PG.eState THEN
      CASE PG.eState OF
            ValidHi:
                    fbLogger(sMsg:='Gauge pressure valid high.', eSevr:=TcEventSeverity.Info);
            ValidLo:
                    fbLogger(sMsg:='Gauge pressure valid low.', eSevr:=TcEventSeverity.Info);
            Valid:
                    fbLogger(sMsg:='Gauge pressure valid.', eSevr:=TcEventSeverity.Info);
            GaugeDisconnected:
                    fbLogger(sMsg:='Gauge Disconnected.', eSevr:=TcEventSeverity.Critical);
            PressInvalid:
                    fbLogger(sMsg:='Gauge pressure invalid.', eSevr:=TcEventSeverity.Warning);
            OoR:
                    fbLogger(sMsg:='Gauge pressure out of range.', eSevr:=TcEventSeverity.Warning);
            Starting:
                    fbLogger(sMsg:='Gauge starting.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := PG.eState;
  END_IF
END_ACTION

FB_ADS

FUNCTION_BLOCK FB_ADS
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
     fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.FIELDBUS);
    tErrorPresent : R_TRIG;
END_VAR


END_FUNCTION_BLOCK

FB_ADS_WATCHDOG

(*This function block is to be used whenever deivce data going to be read over ADS*)
(*The remote plc has to instantiate this function block in order to write a watchdog variable*)
(*that the ADS read function expects to keep checking*)
FUNCTION_BLOCK FB_ADS_WATCHDOG
VAR_INPUT
    sNetId : String; //NetID of the Destination PLC controller
    nPort : uint; // port number
    sVarName : string;// the variable name of the watchdog on the remote plc.
END_VAR
VAR_OUTPUT
    bError:BOOL;
END_VAR
VAR

    fb_WriteWatchdog:FB_WriteWatchdog;
    ftReset_Watchdog: F_TRIG;
    xFirstPass: BOOL:= true;

END_VAR
ftReset_Watchdog(CLK:= fb_WriteWatchdog.bBusy OR xFirstPass);
xFirstPass := false;

(*calling ADS Watchdog function*)
fb_WriteWatchdog(
    bEnable:= TRUE ,
    sNetId:= sNetId,
    nPort:= nPort,
    nIdxGrp:= ,
    nIdxOffs:= ,
    sVarName:= sVarName,
    tWatchdogTime:= t#30ms,
    bSendNow:= ftReset_Watchdog.Q ,
    bBusy=> ,
    nLastCnt=>  ,
    bError=> bError,
    nErrorId=> );


(*Error*)

END_FUNCTION_BLOCK

FB_CMR362

(* For Pfeiffer CMR 362 *)
FUNCTION_BLOCK FB_CMR362
VAR_INPUT
END_VAR
VAR_OUTPUT
            {attribute 'pytmc' := 'pv:'}
            PG : ST_VG;
END_VAR
VAR
    MinPressure: REAL := 7.5E-3; //Torr
    FullScale : REAL := 75.0 ; //Torr
    V : REAL;
    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR
(* For Pfeiffer CMR 362 *)
(*Soft IO Mapping*)
ACT_IO();

(* Real-value calculation *)
If (iTermBits=0) THEN iTermBits := 32767;END_IF
V := 10*INT_TO_REAL(PG.i_iPRESS_R)/iTermBits;


IF V < 0.4 THEN

    PG.eState := GaugeDisconnected; //Most likely not connected

ELSIF V >= 0.4 AND V <= 1.0 THEN
    PG.rPRESS := MinPressure;
    PG.eState := ValidLo;

ELSIF V > 1 AND V <= 9.8 THEN

    PG.rPRESS := LREAL_TO_REAL((V-1)*0.125*FullScale);
    PG.eState := Valid;

ELSE

PG.eState := OoR; //Larger voltage, probably out of range?

END_IF


(* Pressure OK check *)
PG.xPRESS_OK := (PG.rPRESS >= MinPressure);

(*Soft IO Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
PG.i_iPRESS_R :=i_iPRESS_R;
END_ACTION

FB_EbaraDryPump

(*This function block does basic controls for the Ebara EV-S Dry pump. Starts booster pump when appropriate. Turns off pump
in the event of errors/ warnings. Provides interlocking interface.*)
FUNCTION_BLOCK FB_EbaraDryPump EXTENDS FB_Pump
VAR_INPUT
    i_stBPGauge     :       ST_VG; //booster pump interlock gauge
    i_xVlvOpn       :       BOOL; //valve(s) to main system load are open
    i_xExtIlkOK     :       BOOL;

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    stPump  :       ST_EbaraDryPump; //Ebara dry pump structure
END_VAR
VAR_IN_OUT

END_VAR
VAR
    (*IO*)
    q_xMPStart      AT%Q*:  BOOL; //Main Pump start
    q_xBPStart      AT%Q*:  BOOL; // Booster Pump start (this can be started by the pump automatically)
    //Readbacks
    i_xMPStatus     AT%I*:  BOOL; //MP status
    i_xBPStatus     AT%I*:  BOOL; //BP status
    i_xWarning      AT%I*:  BOOL; //Warning status
    i_xAlarmOK      AT%I*:  BOOL; //Alarm, maps to error
    i_xRemote       AT%I*:  BOOL; //Remote control status
END_VAR
(* Ebara Dry Pump Control Routine
A. Wallace
2016-4-29
Applicable to:
EV-S20
EV-S50
EV-S100
EV-S200

Does basic controls for the pump. Starts booster pump when appropriate. Turns off pump
in the event of errors/ warnings. Provides interlocking interface.

Main Pump Operational Note:
Ebara recommends that the pump be exposed to the full system load after the main pump is running.

Booster Pump Operational Note:
The booster pump can either be started automatically by the pump itself (probably based on a timer?)
or it can be started by the PLC. The pressure setpoint is by default 30T. It may be wise to make
sure the pump was exposed to the full system load before starting the booster.

*)

//Mapping
ACT_IO();
stPump.xExtIlk := NOT i_xExtIlkOK;

//Interlock and error/warning
stPump.xIlkOK :=  NOT stPump.xExtIlk AND NOT (stpump.xErr);// OR stpump.xWrn);

//Start/ Stop the pump
IF stpump.xIlkOK THEN
    stpump.q_xMPStart := stpump.pv_xRunSW;
ELSE
    stpump.q_xMPStart := stpump.pv_xRunSW := FALSE;
END_IF

//Booster pump
stpump.xBPIlk := i_xVlvOpn AND stpump.i_xMPStatus AND (i_stBPGauge.rPRESS < stpump.rBPIlkSP) AND i_stbpgauge.xPRESS_OK;
stpump.tonBP(IN:=stpump.xBPIlk);
stpump.q_xBPStart := stpump.tonBP.Q;

(*State evaluation*)
IF stPump.i_xAlarm THEN
    stpump.eState := pumpFAULT;
ELSIF NOT stpump.q_xMPStart AND stPump.i_xAlarm THEN
            stpump.eState := pumpSTOPPED;
ELSIF stpump.q_xMPStart AND NOT stPump.i_xMPStatus THEN
            stpump.eState := pumpSTARTING;
ELSIF stPump.i_xMPStatus OR stPump.i_xBPStatus THEN
            stpump.eState := pumpRUNNING;
ELSIF NOT stPump.i_xMPStatus AND NOT stPump.i_xBPStatus THEN
            stpump.eState := pumpSTOPPED;
ELSE
    stpump.eState := pumpFAULT;
END_IF


//Mapping
ACT_IO();
// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*Outputs*)
q_xMPStart  := stPump.q_xMPStart;
q_xBPStart  := stPump.q_xBPStart;
stPump.q_xRunDo := stPump.q_xMPStart;
(*Inputs*)
stPump.i_xMPStatus:=        i_xMPStatus;
stPump.i_xBPStatus:=        i_xBPStatus;
stPump.i_xWarning:= NOT i_xWarning;
// These are normally closed inputs
stPump.xWrn := NOT i_xWarning;
stPump.i_xAlarm:=   NOT(i_xAlarmOK);
StPump.xErr := NOT(i_xAlarmOK);
stPump.i_xRemote:= i_xRemote;
END_ACTION
ACTION ACT_Logger:
// ILK logger
IF NOT i_xExtIlkOK AND ePrevState = pumpRUNNING THEN
            fbLogger(sMsg:='Lost external interlock while pump was running.', eSevr:=TcEventSeverity.Critical);
END_IF
//STATE Logger

IF ePrevState <> stpump.eState THEN
      CASE stpump.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := stpump.eState;
  END_IF


// Log Action
tAction(CLK:=  stPump.q_xRunDo);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF


// Log FAULT
tFault(CLK:= NOT stPump.i_xAlarm);
IF tFault.Q THEN fbLogger(sMsg:='Pump Lost Alarm OK bit', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

FB_EbaraEVA

(*This function block does basic controls for the Ebara EV-A  pump. Turns off pump
in the event of errors/ warnings. Provides interlocking interface.*)
FUNCTION_BLOCK FB_EbaraEVA EXTENDS FB_Pump
VAR_INPUT
    i_xExtIlkOK     :       BOOL;
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    stPump  :       ST_EbaraEVA; //Ebara dry pump structure
END_VAR
VAR
  // For logging
  (*  fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.VACUUM);
    ePrevState : E_PumpState;
    tErrorPresent : R_TRIG;
    tAction : R_TRIG; // Primary action of this device (OPN_DO, PUMP_RUN, etc.)
    tFault : F_TRIG;*)

(*IO*)
    q_xRunDo AT%Q* : BOOL; // Output signal
    q_xRemote At%Q* :BOOL;// Remote/Local Control
    q_xResetAlarm At%Q* :BOOL;// Remote/Local Control
    i_xAlarmOK AT%I* :BOOL; // Normally closed Alarm bit
    i_xIsRun   AT%I*:BOOL; // Input status
END_VAR
(*MG 2019*)
stPump.xIlkOK := i_xExtIlkOK AND stPump.i_xAlarmOK ;
// Enable the remote signal when the interlock evaluation is ok
//stPump.q_xRemote := stPump.xIlkOK;

IF stPump.xIlkOK THEN
    stPump.q_xRunDO := stPump.pv_xRunSW;
ELSE
            stPump.PV_xRunSW := FALSE;
        stPump.q_xRunDO := FALSE;
END_IF

(*State evaluation*)
IF NOT(stPump.i_xAlarmOK ) THEN
     ePrevState := stpump.eState;
    stpump.eState := pumpFAULT;
ELSIF NOT stpump.q_xRunDo AND stPump.i_xAlarmOK THEN
            stpump.eState := pumpSTOPPED;
ELSIF NOT stpump.q_xRunDo AND NOT stPump.i_xIsRun THEN
            stpump.eState := pumpSTARTING;
ELSIF stPump.i_xIsRun THEN
            stpump.eState := pumpRUNNING;
ELSE
    stpump.eState := pumpFAULT;
END_IF


//Soft IO Mapping
ACT_IO();

// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
stPump.i_xAlarmOK:= i_xAlarmOK;
stPump.xErr:=       NOT(i_xAlarmOK);
stPump.i_xIsRun := i_xIsRun;
(*outputs*)
q_xRunDo:=stPump.q_xRunDo;
q_xRemote:= stPump.q_xRemote;
END_ACTION
ACTION ACT_Logger:
// ILK logger
IF NOT i_xExtIlkOK AND ePrevState = pumpRUNNING THEN
            fbLogger(sMsg:='Lost external interlock while pump was running.', eSevr:=TcEventSeverity.Critical);
END_IF

//STATE Logger
IF ePrevState <> stpump.eState THEN
      CASE stpump.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := stpump.eState;
  END_IF

// Log Action
tAction(CLK:=  stPump.q_xRunDo);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF


// Log FAULT
tFault(CLK:= stPump.i_xAlarmOK);
IF tFault.Q THEN fbLogger(sMsg:='Pump Lost Alarm OK bit', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

FB_Gauge_Interface

(* This function block is created for interface devices between different PLC*)
(* Not all the Variables in the original structure is required, just few signals *)
(* They have to be linked to the custom created variables on the EL6692/5 primary side*)
FUNCTION_BLOCK FB_Gauge_Interface
VAR_INPUT
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv:'}
    VG : ST_VG;
END_VAR
VAR
    i_rPRESS AT%I*: REAL;
    i_xAT_VAC AT%I*: BOOL;
    i_xPRESS_OK AT%I*: BOOL;

END_VAR
(* This function block is created for interface devices between different PLC*)
(* Not all the Variables in the original structure is required, just few signals *)
(* They have to be linked to the custom created variables on the EL6692/5 primary side*)

VG.rPRESS := i_rPRESS;
VG.xAT_VAC := i_xAT_VAC;
VG.xPRESS_OK := i_xPRESS_OK;

END_FUNCTION_BLOCK

FB_GaugeBase

{attribute 'reflection'}
FUNCTION_BLOCK FB_GaugeBase
VAR_INPUT
END_VAR
VAR_OUTPUT

END_VAR
VAR
    //Logging
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.VACUUM);
    ePrevState : E_PressureState;
    tErrorPresent : R_TRIG;
    tAction : R_TRIG; // Primary action of this device (OPN_DO, etc.)
    tOverrideActivated : R_TRIG;

    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
END_VAR


END_FUNCTION_BLOCK

FB_GaugeModbus

FUNCTION_BLOCK FB_GaugeModbus EXTENDS FB_GaugeBase
VAR_INPUT
    IP: STRING;     //IP of Legacy PLC
    nMBAddr :WORD; //Start address of the Register Memory
    iBitOffset:INT :=0; // must be smaller than 32
    tTimeout :TIME:= T#1s;
END_VAR
VAR_OUTPUT
    VG: ST_VG;
    xNoPLCResponse: BOOL;
END_VAR
VAR
    fbPLCInputCoilsRx       :       FB_MBReadInputs;// (Modbus function 2)
    i_PLC_nBits     :       ARRAY [1..4] OF BYTE;
    ftReset : F_TRIG;
    tonRetry : TON;
    ErrCount:INT :=0;
    iIndex :INT;
    iBit:INT;
END_VAR
VAR CONSTANT
    nZero : INT := 0;
    nOne : INT := 1;
    nTwo : INT := 2;
    nThree : INT := 3;
    nFour : INT := 4;
    nFive : INT := 5;
    nSix : INT := 6;
    nSeven : INT := 7;
END_VAR
A_ReadMB();

END_FUNCTION_BLOCK
ACTION A_ReadMB:
(*Credit to Alex Wallace*)
(* Modbus Info for Koyo
Modbus Addresses for
Koyo DL05/06/240/250/260/430/440/450 PLCs
PLC Memory Type             | Modbus start address Decimal (octal) | Function codes
Inputs (X)                    2048 (04000)                                                  2
Special Relays (SP)   3072 (06000)                                                  2
Outputs (Y)                   2048 (04000)                                                  1, 5, 15
Control Relays (C)    3072 (06000)                                                  1, 5, 15
Timer Contacts (T)    6144 (014000)                                                 1, 5, 15
Counter Contacts (CT) 6400 (014400)                                                 1, 5, 15
Stage Status Bits (S) 6144 (012000)                                                 1, 5, 15
*)

// Retry after some time
tonRetry.IN := NOT fbPLCInputCoilsRx.bBusy;
tonRetry.PT := T#10S;
tonRetry();

ftReset(CLK:=fbPLCInputCoilsRx.bBusy);
fbPLCInputCoilsRx.bExecute := ftReset.Q OR tonRetry.Q;

fbPLCInputCoilsRx(sIPAddr:=IP, nTCPPort:=502, nQuantity:=32, nMBAddr:=nMBAddr, cbLength:=SIZEOF(i_PLC_nBits),  pDestAddr:=ADR(i_PLC_nBits), tTimeout:=tTimeout);
//if there's a modbus error, set all incoming bits to zero
IF fbPLCInputCoilsRx.bError THEN
    i_PLC_nBits[1].0 := 0;
    i_PLC_nBits[1].1 := 0;
    i_PLC_nBits[1].2 := 0;
    i_PLC_nBits[1].3 := 0;
    i_PLC_nBits[1].4 := 0;
    i_PLC_nBits[1].5 := 0;
    i_PLC_nBits[1].6 := 0;
    i_PLC_nBits[1].7 := 0;
    i_PLC_nBits[2].0 := 0;
    i_PLC_nBits[2].1 := 0;
    i_PLC_nBits[2].2 := 0;
    i_PLC_nBits[2].3 := 0;
    i_PLC_nBits[2].4 := 0;
    i_PLC_nBits[2].5 := 0;
    i_PLC_nBits[2].6 := 0;
    i_PLC_nBits[2].7 := 0;
    VG.xPRESS_OK := FALSE;
    xNoPLCResponse:= TRUE;
ELSIF NOT ftReset.Q AND fbPLCInputCoilsRx.cbRead > 0 THEN
    xNoPLCResponse:= FALSE;
    VG.rPRESS := 1E-8;
    iIndex:= REAL_TO_INT(iBitOffset/8);
    iBit:= (24 MOD 10);
    VG.xPRESS_OK := M_GetBit(iIndex,iBit);
    fbPLCInputCoilsRx.bExecute :=False;
ELSIF (*ftReset.Q*) NOT (fbPLCInputCoilsRx.bExecute) AND fbPLCInputCoilsRx.cbRead = 0 THEN
    xNoPLCResponse:= TRUE;
END_IF

// Log Action
tAction(CLK:=   fbPLCInputCoilsRx.bError or xNoPLCResponse);
IF tAction.Q THEN
ErrCount:= ErrCount+1;
fbLogger(sMsg:='Modbus read error', eSevr:=TcEventSeverity.Critical);
END_IF
END_ACTION

FB_GCC_Test

FUNCTION_BLOCK FB_GCC_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    PG : ST_VG;
    fb_MKS422: FB_MKS422;
    //fb_MKS500: FB_MKS500;
    i_iPRESS_R AT %Q* :INT;
    cycle:INT :=0;
END_VAR
M_INIT();
M_Interlock();
M_CheckRange();
//M_SelfProtection();
cycle:=cycle+1;

END_FUNCTION_BLOCK

FB_GCM

(* For Baratron Pressure Gauge *)
(*The full-scale pressure times the ratio of the meas.
voltage to full-scale voltage (10V).
The minimum pressure is always going to be 5e-4 * F.S.V.*)
FUNCTION_BLOCK FB_GCM
VAR_IN_OUT

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv:'}
    PG      :       ST_VG;
END_VAR
VAR_INPUT
    i_rFULL_SCALE : REAL;
END_VAR
VAR
    rMinPressure: REAL;
    rHYST_PERC      :       REAL := 0.80;   // Hysteresis percentage
    rV      :REAL;
    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR
(* Baratron Pressure Calc
A. Wallace
2016-6-16
The full-scale pressure times the ratio of the meas.
voltage to full-scale voltage (10V).
The minimum pressure is always going to be 5e-4 * F.S.V.*)
PG.rFULL_SCALE := i_rFULL_SCALE;
rMinPressure := PG.rFULL_SCALE * 5E-4;

If (iTermBits=0) THEN iTermBits := 32767;END_IF
rV :=MAX((10*INT_TO_REAL(PG.i_iPRESS_R)/iTermBits),0);

PG.rPRESS := (PG.rFULL_SCALE/10)*rV;
IF PG.rPRESS > (rMinPressure)THEN
    PG.xPRESS_OK := TRUE;
ELSE
    PG.xPRESS_OK := FALSE;
END_IF

(*IO soft linking*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
PG.i_iPRESS_R :=i_iPRESS_R;
END_ACTION

FB_GPI_Test

FUNCTION_BLOCK FB_GPI_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    fb_MKS275: FB_MKS275;
    fb_MKS317: FB_MKS317;
    i_iPRESS_R AT %Q* :INT;
    cycle:INT :=0;
END_VAR
M_INIT();
M_CheckRange();
cycle:=cycle+1;

END_FUNCTION_BLOCK

FB_ITR90

(* This function block implements the Oerlikon Leybold IONVAC Hot Cathode ITR 90*)
FUNCTION_BLOCK FB_ITR90
VAR_INPUT
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    PG : ST_VG;
END_VAR
VAR
    rV : REAL;      // input voltage
    rC: REAL := -0.125; // a constant in the manual (pressure unit dependent)
    rK: REAL := 1.0;// Calibration factor C (gas type dependent)
    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR
(* This function block implements the Oerlikon Leybold IONVAC Hot Cathode ITR 90*)

(* Real-value calculation *)

rV := 10*INT_TO_REAL(PG.i_iPRESS_R)/iTermBits;
PG.rPRESS := (LREAL_TO_REAL(EXPT(10,(rV-7.75)/0.75+rC))) * rK;


(* Pressure gauge State checks *)

//IF (rV <=9.7 ) AND (rV>=1) THEN
    //PG.eState := Valid; // normal
IF rV >= 0.5 AND rV <= 4.5 THEN
    PG.eState := ValidLo; //LO
ELSIF rV > 4.5 AND rV<= 9.9 THEN
    PG.eState := ValidHi; //HIGH
ELSIF rV < 0.5 THEN
    PG.eState := GaugeDisconnected; //not on
ELSE
    PG.eState := PressInvalid; //other fault - could be no gauge, controller powering up etc
END_IF



(* Pressure gauge OK checks *)
PG.xPRESS_OK := (rV <=9.9 ) AND (rV>=0.5);


(* Setpoint evaluation *)
PG.xAT_VAC := PG.xPRESS_OK AND PG.rPRESS < PG.rVAC_SP;


(*Soft IO Linking*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
PG.i_iPRESS_R :=i_iPRESS_R;
END_ACTION

FB_Kashiyama_VRC

(* Main Kashiyama gate valve permit to be open when:
            1. pump is NORMAL operating status, and
        2. chamber pressure is below HighSP or pump is at LowSpeed state.
    it will close if chamber pressure is below than lowSP for 180s.
*)
FUNCTION_BLOCK FB_Kashiyama_VRC
VAR_IN_OUT

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := ' pv:'}
    q_stValve : ST_VRC;
END_VAR
VAR_INPUT
    Pump : ST_KashiyamaDryPump;//Kashyiama pump
    i_xExtILK_OK : BOOL; //Connect to Interlock logic condition(e.g F_TURBO_VRC_ILK Function), otherwise, Set to True if the valve is not interlocked
    Gauge : ST_VG;// Pressure Gauge
    i_xOverrideMode : BOOL;
END_VAR
VAR
    At_HiVac : BOOL;
    tmrTP : TP;
    tmrPT: TIME := T#180S;
    v1: BOOL;
    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;

    (*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xOPN_DO       AT%Q*: BOOL;

END_VAR
(* Main Kashiyama gate valve permit to be open when:
            1. pump is NORMAL operating status, and
        2. chamber pressure is below HighSP or pump is at LowSpeed state.
    it will close if chamber pressure is below than lowSP for 60s.
*)
(*
      For main Kayshiyama pump's gate valve:
            Pump LowSP is MU100 base pressure (20 mTorr)
            Pump HighSP is 600 Torr;
      Second Kayshiyama pump allow Gas Attenuator operating at higher pressure,
      it's gate valve is to be open when Gas Attenuator chamber pressure is above lowSP.
*)


///Check valve postion
IF NOT q_stValve.i_xClsLS AND q_stValve.i_xOpnLS THEN
    q_stValve.eState:=OPEN;
ELSIF q_stValve.i_xClsLS AND NOT q_stValve.i_xOpnLS THEN
    q_stValve.eState:=CLOSED;
ELSIF NOT q_stValve.i_xClsLS AND NOT q_stValve.i_xOpnLS THEN
    q_stValve.eState:=MOVING;
ELSE
    q_stValve.eState:=INVALID;
END_IF

//valve open interlock
q_stValve.xOPN_OK := Pump.i_xIsRun AND Pump.RdyTmr <= 0.0 AND (Gauge.rPRESS < Pump.HighSP OR Pump.q_xLspdDo) AND i_xExtILK_OK;


(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=q_stValve.xOPN_OK, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q THEN q_stValve.pv_xOvrdOpn :=FALSE; END_IF

//Override timer
tonOvrd(IN:=q_stValve.pv_xOvrdOpn, PT:=T#10S);


(* Here's where the valve closes *)
IF NOT q_stValve.xOPN_OK AND NOT tonOvrd.Q THEN
q_stValve.pv_xOPN_SW := FALSE;
END_IF

At_HiVac := Gauge.xPRESS_OK AND Gauge.rPRESS < Pump.LowSP;
tmrTP (IN := At_HiVac AND q_stValve.q_xOPN_DO , PT := tmrPT);

IF At_HiVac AND q_stValve.q_xOPN_DO AND NOT tmrTP.Q THEN
    q_stValve.pv_xOPN_SW := FALSE ;
END_IF

(* Here's where the valve opens *)
q_stValve.q_xOPN_DO := (q_stValve.pv_xOPN_SW AND q_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);

(*Soft IO Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
q_stValve.i_xOpnLS :=       i_xOpnLS;
q_stValve.i_xClsLS:=        i_xClsLS;
(*outputs*)
q_xOPN_DO:= q_stValve.q_xOPN_DO;
END_ACTION

FB_KashiyamaPump

(* This function block does basic controls FOR the Kashiyama pump. Turns off pump
in the event of errors/ warnings. Provides interlocking interface.*)
FUNCTION_BLOCK FB_KashiyamaPump
VAR_IN_OUT

END_VAR
VAR_INPUT

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv:'}
    q_stPump : ST_KashiyamaDryPump;
END_VAR
VAR
    TONtmr : TON;
    resetTmr : TON;

    (* Output *)
    q_xRunDO        AT%Q*:  BOOL;
    q_xResetDo      AT%Q*:  BOOL;
    q_xLspdDo       AT%Q*:  BOOL;

(* Input *)
    i_xLocal        AT%I*:  BOOL;
    i_xAlarm        AT%I*:  BOOL;
    i_xWarning      AT%I*:  BOOL;
    i_xIsRun        AT%I*:  BOOL;
END_VAR
//Kashiyama Dry pump outputs at high level indicate no warning, no alarm and remotely
q_stPump.xIlkOK := q_stPump.i_xLocal AND q_stPump.i_xWarningOK AND q_stPump.i_xAlarmOK;

IF q_stPump.xIlkOK THEN
    q_stPump.q_xRunDO := q_stPump.pv_xRunSW;
    ELSE
            q_stPump.PV_xRunSW := FALSE;
        q_stPump.q_xRunDO := FALSE;
END_IF

q_stPump.xLspd_IlkENA := q_stPump.q_xRunDO;

IF q_stPump.xLspd_IlkENA THEN
    q_stPump.q_xLspdDo := q_stPump.xLspdSW;
    ELSE
            q_stPump.xLspdSW := FALSE;
        q_stPump.q_xLspdDo := FALSE;
END_IF

//resetTmr (In := iq_stPump.xResetSW, PT := T#2S);
//IF resetTmr.Q THEN
//  iq_stPump.xResetSW := FALSE;
//END_IF

q_stPump.q_xResetDo := NOT q_stPump.i_xIsRun AND q_stPump.xResetSW;

//countdown Timer
TONtmr (IN := q_stPump.i_xIsRun, PT := T#20S);
q_stPump.RdyTmr := TO_REAL(TONtmr.PT) - TO_REAL(TONtmr.ET);

(*State evaluation*)
IF NOT q_stPump.i_xAlarmOK THEN
    q_stPump.eState := pumpFAULT;
ELSIF NOT q_stPump.q_xRunDo AND q_stPump.i_xAlarmOK THEN
            q_stPump.eState := pumpSTOPPED;
ELSIF NOT q_stPump.q_xRunDo AND NOT q_stPump.i_xIsRun THEN
            q_stPump.eState := pumpSTARTING;
ELSIF q_stPump.i_xIsRun THEN
            q_stPump.eState := pumpRUNNING;
ELSE
    q_stPump.eState := pumpFAULT;
END_IF

//Mapping
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(* Output *)
    q_xRunDO        := q_stPump.q_xRunDO;
    q_xResetDo := q_stPump.q_xResetDo;;
    q_xLspdDo := q_stPump.q_xLspdDo;;

(* Input *)
    q_stPump.i_xLocal := i_xLocal;
    q_stPump.i_xAlarmOK :=  i_xAlarm;
    q_stPump.i_xWarningOK :=        i_xWarning;
    q_stPump.i_xIsRun :=    i_xIsRun;
END_ACTION

FB_MKS248

(* MKS248 valve using MKS1249 Drive Module *)
FUNCTION_BLOCK FB_MKS248
VAR_INPUT
    i_xExtIlkOK     :       BOOL; //External Interlock, SET to TRUE if not used
    i_rReqPos       :       REAL; //Requested position
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv:'}
    iq_stVFN        :       ST_VCN; //Needle valve structure
END_VAR
VAR CONSTANT
    rOpenVoltage    :       REAL := 9.8;
    rCloseVoltage   :       REAL := 0;
END_VAR
VAR
    // Requested voltage
    rReqVoltage: REAL := 0;
    (*IO*)
    q_iRawPosition AT%Q* :INT;
END_VAR
(* MKS248 valve using MKS1249 Drive Module *)


// Interlocking
iq_stVFN.xIlkOK := i_xExtIlkOK;
(*Checking which Control mode is selected*)
IF iq_stVFN.xIlkOK THEN
    IF iq_stVFN.eValveControl = OpenValve THEN
            iq_stVFN.rReqPosition := iq_stVFN.rUpperLimit;(*Percentage*)// iq_stVCN.rUpperLimit;
    ELSIF iq_stVFN.eValveControl = CloseValve THEN
            iq_stVFN.rReqPosition := 0; (*Percentage*)
    ELSIF iq_stVFN.eValveControl = ManualControl THEN
            iq_stVFN.rReqPosition := LIMIT(0, iq_stVFN.rReqPosition, iq_stVFN.rUpperLimit);
    ELSIF iq_stVFN.eValveControl = PressureControl THEN
            iq_stVFN.rReqPosition := LIMIT(0, i_rReqPos, iq_stVFN.rUpperLimit);
    END_IF
ELSE
    iq_stVFN.rReqPosition := 0;
    iq_stVFN.eValveControl := CloseValve;
END_IF

// Requested Voltage calculation
rReqVoltage := iq_stVFN.rReqPosition * (rOpenVoltage-rCloseVoltage)/100 + rCloseVoltage;
rReqVoltage := LIMIT(rCloseVoltage, rReqVoltage, rOpenVoltage); //The requested voltage should remain within this range
//Raw position calc
iq_stVFN.q_iRawPosition := REAL_TO_INT( 32767/10 * rReqVoltage);
(*IO Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*outputs*)
q_iRawPosition := iq_stVFN.q_iRawPosition;
END_ACTION

FB_MKS275

(* This function block is used to provide protection and automatic turn on of ion gauges,
 it also manages the turn on of the AT_VAC boolean, and checks to make sure the pressure is good *)
(* For MKS 275 mini-convectron *)
{attribute 'no_check'}
FUNCTION_BLOCK FB_MKS275 EXTENDS FB_GaugeBase
VAR_IN_OUT

END_VAR
VAR_INPUT
END_VAR

VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    PG : ST_VG;
END_VAR
VAR
    V : REAL;
    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
    Vlowest: REAL := 10;
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR

VAR CONSTANT
    MinPressure: REAL := 1E-4;
    rDeadband : REAL :=0.05;
    rValidLoBoundary        :       REAL := 0.375; //  0.375V as per manual page 27
    rValidHiBoundary        :       REAL := 5.659;// 5.534; // manual page 27
    rDisconnectedBoundary   :       REAL := 0.3;
END_VAR
(* This function block is used to provide protection and automatic turn on of ion gauges,
 it also manages the turn on of the AT_VAC boolean, and checks to make sure the pressure is good *)
(* For MKS 275 mini-convectron *)

//Default setpoint 50 mT
IF PG.rVAC_SP = 0 THEN
    PG.rVAC_SP := 5E-2;
END_IF

(* Real-value calculation *)
If (iTermBits=0) THEN iTermBits := 32767;END_IF
V := 10*INT_TO_REAL(PG.i_iPRESS_R)/iTermBits;

Vlowest := MIN(V, Vlowest);

IF V > rDisconnectedBoundary AND V < rValidLoBoundary  THEN
    PG.rPRESS := MinPressure;
    PG.eState := ValidLo;

ELSIF V >= rValidLoBoundary AND V < rValidLoBoundary +rDeadband  THEN
    PG.rPRESS := MAX( MinPressure, LREAL_TO_REAL( -0.02585 + 0.03767*V + 0.04563*EXPT(V,2)+ 0.1151*EXPT(V,3) - 0.04158*EXPT(V,4)+ 0.008737*EXPT(V,5)) );
    PG.eState := ValidLo;

ELSIF V >= rValidLoBoundary AND V < 2.842 THEN
    PG.rPRESS := MAX( MinPressure, LREAL_TO_REAL( -0.02585 + 0.03767*V + 0.04563*EXPT(V,2)+ 0.1151*EXPT(V,3) - 0.04158*EXPT(V,4)+ 0.008737*EXPT(V,5)) );
    PG.eState := Valid;

ELSIF V >= 2.842 AND V < 4.945 THEN
    PG.rPRESS := LREAL_TO_REAL((0.1031-0.02322*V+0.07229*EXPT(V,2))/(1-0.3986*V+0.07438*EXPT(V,2)-0.006866*EXPT(V,3)));
    PG.eState := Valid;

ELSIF V >= 4.945 AND V < rValidHiBoundary THEN
    PG.rPRESS := LREAL_TO_REAL((100.624-20.5623*V)/(1-0.37679*V+0.0348656*EXPT(V,2)));
    PG.eState := ValidHi;

ELSIF V <= rDisconnectedBoundary THEN
    PG.eState := GaugeDisconnected; //Most likely not connected
    PG.rPRESS := -1;

ELSE
    PG.eState := OoR; //Larger voltage, probably out of range?
    PG.rPRESS := -1;
END_IF

(* Protection Functions *)
(* If the PG pressure is greater than the IG.PRO_SP then the gauge is disabled *)
(* If the PG pressure is less than the IG.PRO_SP then the gauge is enabled *)
(* This FB also implements some hysteresis so the gauge doesn't have rapid power cycling while near the turn on boundary *)

(* Pressure OK check *)
PG.xPRESS_OK := (PG.rPRESS >= MinPressure);

(* Setpoint evaluation *)
PG.xAT_VAC := PG.xPRESS_OK AND (PG.rPRESS<=PG.rVAC_SP);

(*Logger*)
ACT_Logger();

(*Soft IO Mapping *)
IO();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
//STATE Logger
if (PG.xLog) THEN
    IF ePrevState <> PG.eState THEN
      CASE PG.eState OF
            ValidHi:
                    fbLogger(sMsg:='Gauge pressure valid high.', eSevr:=TcEventSeverity.Info);
            ValidLo:
                    fbLogger(sMsg:='Gauge pressure valid low.', eSevr:=TcEventSeverity.Info);
            Valid:
                    fbLogger(sMsg:='Gauge pressure valid.', eSevr:=TcEventSeverity.Info);
            GaugeDisconnected:
                    fbLogger(sMsg:='Gauge Disconnected.', eSevr:=TcEventSeverity.Critical);
            PressInvalid:
                    fbLogger(sMsg:='Gauge pressure invalid.', eSevr:=TcEventSeverity.Warning);
            OoR:
                    fbLogger(sMsg:='Gauge pressure out of range.', eSevr:=TcEventSeverity.Warning);
            Starting:
                    fbLogger(sMsg:='Gauge starting.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := PG.eState;
  END_IF
END_IF
END_ACTION
ACTION IO:
PG.i_iPRESS_R :=i_iPRESS_R;
PG.sPath := sPath;
END_ACTION

FB_MKS317

(* This function is for the Pirani MKS 317 connected to a 937A/B *)
(* This function block is used to provide protection and automatic turn on of ion gauges,
 it also manages the turn on of the AT_VAC boolean, and checks to make sure the pressure is good *)

FUNCTION_BLOCK FB_MKS317 Extends FB_GaugeBase
VAR_IN_OUT
END_VAR
VAR_INPUT
    b937A :BOOL := FALSE; // True if this gauge is connected to MKS937A controller, False if connected to MKS937B controller
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    PG : ST_VG;
END_VAR
VAR
    rV      :       REAL;

    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR

VAR CONSTANT
    rMinPressure: REAL := 1E-4;
    rDefaultVAC_SP: REAL :=5E-2; // Default 50 mT
    rDisconnectedBoundary   :       REAL := 0.10;
    rValidLoBoundary        :       REAL := 0.22;
    rValidBoundaryMin       :       REAL := 0.6;
    rValidHiBoundary        :       REAL := 9.7;
    rValidHiBoundaryMax     :       REAL := 9.9;
    rNoSensorBoundary       :       REAL := 10;
END_VAR
(* 937B Logarithmic Output Conversion *)
(* 5-20-2016, Alex Wallace and Scott Stubbs *)
(* 09-28-2018, Margaret Ghaly *)
(* This function will read the pressure from any gauge on a 937B. *)

rV := 10*INT_TO_REAL(PG.i_iPRESS_R)/32767;
if (b937A) THEN
    PG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV)/0.6)-12)); //manual page 61 MKS937A
    ELSE
    PG.rPRESS := LREAL_TO_REAL(EXPT(10,(rV-7.2)/0.6));      //manual page 73 MKS937B
END_IF


//Default setpoint
IF PG.rVAC_SP = 0 THEN
    PG.rVAC_SP := rDefaultVAC_SP;
END_IF

IF (rV <=rValidHiBoundary ) AND (rV>=rValidBoundaryMin) THEN
    PG.eState := Valid; // normal
ELSIF rV >= rDisconnectedBoundary AND rV <= rValidLoBoundary THEN
    PG.eState := ValidLo; //LO
    PG.rPRESS := rMinPressure;
ELSIF rV > rValidHiBoundary AND rV<= rValidHiBoundaryMax THEN
    PG.eState := ValidHi; //HIGH
ELSIF rV < rDisconnectedBoundary THEN
    PG.eState := GaugeDisconnected; //not on
    PG.rPRESS := -1;
ELSE
    PG.eState := PressInvalid; //other fault - could be no gauge, controller powering up etc
    PG.rPRESS := -1;
END_IF

(* Pressure OK check *)
(* Pressure gauge OK checks *)
PG.xPRESS_OK := (rV <=rValidHiBoundary ) AND (rV>=rDisconnectedBoundary);
//PG.xPRESS_OK := (PG.rPRESS >= rMinPressure);

(* Setpoint evaluation *)
PG.xAT_VAC := (*(PG.eState =Valid)*) PG.xPRESS_OK AND PG.rPRESS <= PG.rVAC_SP;

(*Logger*)
ACT_Logger();

(*soft io*)
IO();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
//STATE Logger
IF (PG.xLog) THEN
    IF ePrevState <> PG.eState THEN
              CASE PG.eState OF
                    ValidHi:
                            fbLogger(sMsg:='Gauge pressure valid high.', eSevr:=TcEventSeverity.Info);
                    ValidLo:
                            fbLogger(sMsg:='Gauge pressure valid low.', eSevr:=TcEventSeverity.Info);
                    Valid:
                            fbLogger(sMsg:='Gauge pressure valid.', eSevr:=TcEventSeverity.Info);
                    GaugeDisconnected:
                            fbLogger(sMsg:='Gauge Disconnected.', eSevr:=TcEventSeverity.Critical);
                    PressInvalid:
                            fbLogger(sMsg:='Gauge pressure invalid.', eSevr:=TcEventSeverity.Warning);
                    OoR:
                            fbLogger(sMsg:='Gauge pressure out of range.', eSevr:=TcEventSeverity.Warning);
                    Starting:
                            fbLogger(sMsg:='Gauge starting.', eSevr:=TcEventSeverity.Info);
              END_CASE
              ePrevState := PG.eState;
      END_IF
 END_IF
END_ACTION
ACTION IO:
PG.i_iPRESS_R :=i_iPRESS_R;

PG.sPath := sPath;
END_ACTION

FB_MKS317A

(*Deprecated*)
(* This function is for the Pirani MKS 317 connected to a 937A*)
(* This function block is used to provide protection and automatic turn on of ion gauges,
 it also manages the turn on of the AT_VAC boolean, and checks to make sure the pressure is good *)

FUNCTION_BLOCK FB_MKS317A  Extends FB_GaugeBase
VAR_IN_OUT

END_VAR
VAR_INPUT

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    PG : ST_VG;
END_VAR
VAR
    rV      :       REAL;
    rMinPressure: REAL := 1E-3;
    rDefaultVAC_SP: REAL :=5E-2; // Default 50 mT
    rValidLoBoundary        :       REAL := 0.2;//Manual page 61
    rValidHiBoundary        :       REAL := 9.7; //manual oage 61
    rDisconnectedBoundary   :       REAL := 0.17;
    rNoSensorBoundary       :       REAL := 10;
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR
(* 937B Logarithmic Output Conversion *)
(* 5-20-2016, Alex Wallace and Scott Stubbs *)
(* 09-28-2018, Margaret Ghaly *)
(* This function will read the pressure from any gauge on a 937A. *)

rV := 10*INT_TO_REAL(PG.i_iPRESS_R)/32767;
PG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV)/0.6)-12)); //manual page 61 937A


//Default setpoint
IF PG.rVAC_SP = 0 THEN
    PG.rVAC_SP := rDefaultVAC_SP;
END_IF

IF (rV <=9.7 ) AND (rV>=0.6) THEN
    PG.eState := Valid; // normal
ELSIF rV >= 0.18 AND rV <= 0.22 THEN
    PG.eState := ValidLo; //LO
    PG.rPRESS := rMinPressure;
ELSIF rV > 9.7 AND rV<= 9.9 THEN
    PG.eState := ValidHi; //HIGH
ELSIF rV < 0.18 THEN
    PG.eState := GaugeDisconnected; //not on
    PG.rPRESS := -1;
ELSE
    PG.eState := PressInvalid; //other fault - could be no gauge, controller powering up etc
    PG.rPRESS := -1;
END_IF

(* Pressure OK check *)
(* Pressure gauge OK checks *)
PG.xPRESS_OK := (rV <=rValidHiBoundary ) AND (rV>=rDisconnectedBoundary);
//PG.xPRESS_OK := (PG.rPRESS >= rMinPressure);

(* Setpoint evaluation *)
PG.xAT_VAC := (*(PG.eState =Valid)*) PG.xPRESS_OK AND PG.rPRESS <= PG.rVAC_SP;

(*Logger*)
ACT_Logger();
(*soft io*)
IO();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
IF (PG.xLog) THEN
//STATE Logger
IF ePrevState <> PG.eState THEN
      CASE PG.eState OF
            ValidHi:
                    fbLogger(sMsg:='Gauge pressure valid high.', eSevr:=TcEventSeverity.Info);
            ValidLo:
                    fbLogger(sMsg:='Gauge pressure valid low.', eSevr:=TcEventSeverity.Info);
            Valid:
                    fbLogger(sMsg:='Gauge pressure valid.', eSevr:=TcEventSeverity.Info);
            GaugeDisconnected:
                    fbLogger(sMsg:='Gauge Disconnected.', eSevr:=TcEventSeverity.Critical);
            PressInvalid:
                    fbLogger(sMsg:='Gauge pressure invalid.', eSevr:=TcEventSeverity.Warning);
            OoR:
                    fbLogger(sMsg:='Gauge pressure out of range.', eSevr:=TcEventSeverity.Warning);
            Starting:
                    fbLogger(sMsg:='Gauge starting.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := PG.eState;
  END_IF
 END_IF
END_ACTION
ACTION IO:
PG.i_iPRESS_R :=i_iPRESS_R;
PG.sPath := sPath;
END_ACTION

FB_MKS392

(* This function block implements the MKS392 gauge*)
FUNCTION_BLOCK FB_MKS392
VAR_INPUT
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    PG : ST_VG;
END_VAR
VAR
    rV : REAL;      // input voltage
    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR
(* Real-value calculation *)
rV := 10*INT_TO_REAL(PG.i_iPRESS_R)/iTermBits;
PG.rPRESS := (LREAL_TO_REAL(EXPT(10,(2*rV)-11)));


(* Pressure gauge State checks *)

IF rV >= 0.5 AND rV <= 3.5 THEN
    PG.eState := ValidLo; //LO
ELSIF rV > 3.5 AND rV<= 6.5 THEN
    PG.eState := ValidHi; //HIGH
ELSIF rV < 0.5 THEN
    PG.eState := GaugeDisconnected; //not on
ELSE
    PG.eState := PressInvalid; //other fault - could be no gauge, controller powering up etc
END_IF



(* Pressure gauge OK checks *)
PG.xPRESS_OK := (rV <=6.5 ) AND (rV>=0.5);


(* Setpoint evaluation *)
PG.xAT_VAC := PG.xPRESS_OK AND PG.rPRESS < PG.rVAC_SP;


(*Soft IO Linking*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
PG.i_iPRESS_R :=i_iPRESS_R;
END_ACTION

FB_MKS422

(* This function is for the Cold Cathode MKS 422 connected to a 937A/B *)
(*This function provides ILK and Set Point Protection for the Cold Cathode*)
FUNCTION_BLOCK FB_MKS422 EXTENDS FB_GaugeBase
VAR_IN_OUT

END_VAR
VAR_INPUT
            PG      :       ST_VG; // Pirani Gauge Structure used to Interlock the Cold Cathode
            b937A :BOOL:=FALSE; // True if this gauge is connected to MKS937A controller, False if connected to MKS937B controller
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    IG : ST_VG; // The Cold Cathode Data Structure
END_VAR
VAR
    rV      :       REAL;

    timer:TON;
    (*IOs to be linked*)
    /// Controls and I/Os
    i_iPRESS_R AT %I* :INT; // input Pressure // Link to analog Input
    q_xHV_DIS AT %Q* : BOOL := True; // Disable Gauge High Voltage when True // 'TcLinkTo' (EL2794) ^Output
END_VAR
VAR CONSTANT
    MinPressure: REAL := 1E-10;
    cDefaultPressure : REAL := 0;
END_VAR
(* 937B Logarithmic Output Conversion *)
(* 5-20-2016, Alex Wallace and Scott Stubbs *)
(* 09-28-2018, Margaret Ghaly *)
(* This function will read the pressure from any gauge on a 937B. *)

(*Soft IO Linking*)
IO();

(*Pressure readbacks*)
rV := 10*INT_TO_REAL(IG.i_iPRESS_R)/32767;
if (b937A) THEN
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV)/0.6)-12)); //manual page 61 MKS937A
    ELSE
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,(rV-7.2)/0.6));      //manual page 73 MKS937B
END_IF



(* Pressure gauge State checks *)
IF (rV <=9.6 ) AND (rV>=0.6) THEN
    IG.eState := Valid; // normal
ELSIF rV >= 0.18 AND rV <= 0.22 THEN
    IG.eState := ValidLo; //LO
ELSIF rV > 9.6 AND rV<= 9.9 THEN
    IG.eState := ValidHi; //HIGH
ELSIF rV < 0.18 THEN //
    IG.eState := GaugeDisconnected; //not on
    IG.rPRESS :=cDefaultPressure;
ELSE
    IG.eState := PressInvalid; //other fault - could be no gauge, controller powering up etc
    IG.rPRESS :=cDefaultPressure;
END_IF


(* Ion Gauge Protection Functions *)
(* If the PG pressure is greater than the VG.PRO_SP then the gauge is disabled *)
(* If the PG pressure is less than the VG.PRO_SP then the gauge is enabled *)
(* This FB also implements some hysteresis so the gauge doesn't have rapid power cycling while near the turn on boundary *)

    IF (PG.rPRESS <= IG.rPRO_SP) THEN
             IG.q_xHV_DIS := NOT IG.xHV_SW;
             IG.xILKOk := TRUE;
    ELSIF NOT IG.q_xHV_DIS AND timer.Q THEN
            IF IG.rPRESS > (IG.rPRO_SP + IG.rHYS_PR) OR PG.rPRESS > (IG.rPRO_SP + IG.rHYS_PR) THEN
            IG.q_xHV_DIS := TRUE;
            IG.xHV_SW := FALSE;
            END_IF
    ELSE
            IG.q_xHV_DIS := TRUE;
            IG.xHV_SW := FALSE;
            IG.xILKOk := FALSE;
    END_IF




(* Pressure gauge OK checks *)
IG.xPRESS_OK := (rV <=9.6 ) AND (rV>=0.6);


(* Setpoint evaluation *)
IG.xAT_VAC := IG.xPRESS_OK AND IG.rPRESS < IG.rVAC_SP;


(*Logger*)
ACT_Logger();
(*Soft IO Linking*)
// check ethercat Diagnostics
IO();

timer(IN:= NOT IG.q_xHV_DIS, PT:= T#2s);

END_FUNCTION_BLOCK
ACTION ACT_Logger:
// ILK logger
IF (IG.xLog) THEN
    IF NOT IG.xILKOk AND NOT IG.q_xHV_DIS THEN
                    fbLogger(sMsg:='Lost external interlock while gauge was on.', eSevr:=TcEventSeverity.Critical);
    END_IF


    // Log Action
    tAction(CLK:=  IG.xHV_SW);
    IF tAction.Q THEN fbLogger(sMsg:='Ion gauge commanded to switch on', eSevr:=TcEventSeverity.Info); END_IF



    //STATE Logger
    IF ePrevState <> IG.eState THEN
              CASE IG.eState OF
                    ValidHi:
                            fbLogger(sMsg:='Gauge pressure valid high.', eSevr:=TcEventSeverity.Info);
                    ValidLo:
                            fbLogger(sMsg:='Gauge pressure valid low.', eSevr:=TcEventSeverity.Info);
                    Valid:
                            fbLogger(sMsg:='Gauge pressure valid.', eSevr:=TcEventSeverity.Info);
                    GaugeDisconnected:
                            fbLogger(sMsg:='Gauge Disconnected.', eSevr:=TcEventSeverity.Critical);
                    PressInvalid:
                            fbLogger(sMsg:='Gauge pressure invalid.', eSevr:=TcEventSeverity.Warning);
                    OoR:
                            fbLogger(sMsg:='Gauge pressure out of range.', eSevr:=TcEventSeverity.Warning);
                    Starting:
                            fbLogger(sMsg:='Gauge starting.', eSevr:=TcEventSeverity.Info);
              END_CASE
              ePrevState := IG.eState;
      END_IF
END_IF
END_ACTION
ACTION IO:
(*soft link inputs*)
IG.i_iPRESS_R:=     i_iPRESS_R;

(*soft link outputs*)
q_xHV_DIS := IG.q_xHV_DIS;

IG.sPath := sPath;
END_ACTION

FB_MKS500

(* This function is for the Cold Cathode MKS 500. If connected to Beckhoff EP boxes.
Set the EP bit to TRUE, this is necessary for the MKS500-to-EP box interface beacuse the EP
boxes do not natively support the 5v IO signals on the MKS500 gauge.*)
(*This function provides ILK and Set Point Protection for the Cold Cathode*)
(* 500 Logarithmic Output Conversion, factory default configuration *)
FUNCTION_BLOCK FB_MKS500 EXTENDS FB_GaugeBase
VAR_IN_OUT

END_VAR
VAR_INPUT
    PG      :       ST_VG;
    bEP : BOOL :=FALSE; // Set to True if This Gauge is connected to EP BOX and not EL Terminals
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
    pv:
    '}
    IG : ST_VG;
END_VAR
VAR
    rV : REAL;
    GaugeTurnOnTmr : TON;
    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
    (*IOs to be linked*)
    /// Controls and I/Os
    i_iPRESS_R AT %I* :INT; // input Pressure // Link to analog Input
    q_xHV_DIS AT %Q* : BOOL; // Enable High Voltage when True // 'TcLinkTo' (EP2624) ^Output
    // only for EL and ES terminal
    i_xHV_ON AT %I* : BOOL; //  True when High Voltage is on  // 'TcLinkTo' (EL1124) ^Input
    i_xDisc_Active AT %I* : BOOL;// Discharge Current Active // 'TcLinkTo' (EL1124) ^Input

END_VAR

VAR CONSTANT
    // Ranges has to match the configurator software
    pBase : REAL :=1.0E-10;//default curve base pressure is 1E-10. Confusing since can't actually read that low using analog out.
    vBase : REAL := 1;
    vDisconnected : REAL:= 0.5;
    vSlope : REAL := 1.0;
    vGaugeOff: REAL := 9.8;
    vNoDischarge: REAL := 9.3;
    MinPressure: REAL := 1E-10;
    cDefaultPressure : REAL := 0;
    rDeadband : REAL :=0.3;
END_VAR
(* 500 Logarithmic Output Conversion, factory default configuration *)
If (iTermBits=0) THEN iTermBits := 32767;END_IF
rV := 10*INT_TO_REAL(IG.i_iPRESS_R)/iTermBits;

(* Set Guage State based on the Analog voltage*)
IF rV < vDisconnected THEN
    IG.eState := GaugeDisconnected;
    IG.rPRESS := cDefaultPressure;
ELSIF rV >= vDisconnected AND rV < (vNoDischarge -rDeadband)  THEN
    IG.eState := Valid;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
ELSIF rV >= (vNoDischarge -rDeadband) AND rV <(vGaugeOff -rDeadband)   THEN
    IG.eState := Starting;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
ELSIF rV >= (vGaugeOff -rDeadband) THEN
    IG.eState := Off;
    //IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
    IG.rPRESS := cDefaultPressure;
ELSE
    IG.eState := OoR;
    //IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
    IG.rPRESS := cDefaultPressure;
END_IF

(* Set Guage State based on the inputs HV ON and Discharge current*)
If (NOT bEP) THEN
    IF IG.i_xHV_ON AND NOT IG.i_xDisc_Active THEN
            IG.eState := Starting;
            IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
    ELSIF IG.i_xHV_ON AND IG.i_xDisc_Active THEN
            IG.eState := Valid;
            IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
    ELSIF NOT IG.i_xHV_ON AND NOT IG.i_xDisc_Active THEN
            IG.eState := Off;
            IG.rPRESS := cDefaultPressure;
    END_IF
END_IF

(* Ion Gauge Protection Functions *)
(* If the PG pressure is greater than the VG.PRO_SP then the gauge is disabled *)
(* If the PG pressure is less than the VG.PRO_SP then the gauge is enabled *)
(* This FB also implements some hysteresis so the gauge doesn't have rapid power cycling while near the turn on boundary *)


IF PG.rPRESS <= IG.rPRO_SP AND PG.xPRESS_OK THEN
    IG.q_xHV_DIS := IG.xHV_SW;
    IG.xILKOk := TRUE;
ELSIF IG.q_xHV_DIS THEN
    IF IG.rPRESS > (IG.rPRO_SP + IG.rHYS_PR)  OR PG.rPRESS > (IG.rPRO_SP + IG.rHYS_PR) THEN
            IG.q_xHV_DIS := FALSE;
            IG.xHV_SW := FALSE;
    END_IF
ELSE
    IG.q_xHV_DIS := FALSE;
    IG.xHV_SW := FALSE;
    IG.xILKOk := FALSE;
END_IF

(* Pressure gauge OK checks *)

GaugeTurnOnTmr(IN:=IG.q_xHV_DIS, PT:=T#10S, Q=>IG.xTurnOnTime);


//Backwards compatibility
IG.xPRESS_OK := (IG.eState = Valid) OR IG.xBAKEOUT;

(* Setpoint evaluation *)
IG.xAT_VAC := (IG.xPRESS_OK AND (IG.rPRESS < IG.rVAC_SP)) OR IG.xBAKEOUT AND (IG.eState = Valid) ;

ACT_Logger();
(*Soft IO Linking*)
// check ethercat Diagnostics
IO();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
// ILK logger
IF (IG.xLog) THEN
    IF NOT IG.xILKOk AND IG.q_xHV_DIS THEN
                    fbLogger(sMsg:='Lost external interlock while gauge was on.', eSevr:=TcEventSeverity.Critical);
    END_IF


    // Log Action
    tAction(CLK:=   IG.xHV_SW);
    IF tAction.Q THEN fbLogger(sMsg:='Ion gauge commanded to switch on', eSevr:=TcEventSeverity.Info); END_IF



    //STATE Logger

    IF ePrevState <> IG.eState THEN
              CASE IG.eState OF
                    ValidHi:
                            fbLogger(sMsg:='Gauge pressure valid high.', eSevr:=TcEventSeverity.Info);
                    ValidLo:
                            fbLogger(sMsg:='Gauge pressure valid low.', eSevr:=TcEventSeverity.Info);
                    Valid:
                            fbLogger(sMsg:='Gauge pressure valid.', eSevr:=TcEventSeverity.Info);
                    GaugeDisconnected:
                            fbLogger(sMsg:='Gauge Disconnected.', eSevr:=TcEventSeverity.Critical);
                    PressInvalid:
                            fbLogger(sMsg:='Gauge pressure invalid.', eSevr:=TcEventSeverity.Warning);
                    OoR:
                            fbLogger(sMsg:='Gauge pressure out of range.', eSevr:=TcEventSeverity.Warning);
                    Starting:
                            fbLogger(sMsg:='Gauge starting.', eSevr:=TcEventSeverity.Info);
              END_CASE
              ePrevState := IG.eState;
      END_IF
END_IF
END_ACTION
ACTION IO:
(*soft link inputs*)
IG.i_iPRESS_R:=     i_iPRESS_R;
IG.i_xHV_ON:= i_xHV_ON;
IG.i_xDisc_Active:=i_xDisc_Active;
(*soft link outputs*)
q_xHV_DIS := IG.q_xHV_DIS;

IG.sPath := sPath;
END_ACTION

FB_MKS500_EP

(*Deprecated*)
(* This function is for the Cold Cathode MKS 500 connected to Beckhoff EP boxes.
A separate function block is necessary for the MKS500-to-EP box interface beacuse the EP
boxes do not natively support the 5v IO signals on the MKS500 gauge.*)
(*This function provides ILK and Set Point Protection for the Cold Cathode*)
(* 500 Logarithmic Output Conversion, factory default configuration *)
FUNCTION_BLOCK FB_MKS500_EP EXTENDS FB_GaugeBase
VAR_IN_OUT

END_VAR
VAR_INPUT

    PG      :       ST_VG;
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
    pv:
    '}
    IG : ST_VG;
END_VAR
VAR
    rV : REAL;
    GaugeTurnOnTmr : TON;

    (*IOs to be linked*)
    /// Controls and I/Os
    i_iPRESS_R AT %I* :INT; // input Pressure // Link to analog Input
    q_xHV_DIS AT %Q* : BOOL; // Enable High Voltage when True // 'TcLinkTo' (EP2624) ^Output
    //
END_VAR

VAR CONSTANT
    // Ranges has to match the configurator software
    pBase : REAL :=1.0E-10;//default curve base pressure is 1E-10. Confusing since can't actually read that low using analog out.
    vBase : REAL := 1;
    vDisconnected : REAL:= 0.5;
    vSlope : REAL := 1.0;
    vGaugeOff: REAL := 9.8;
    vNoDischarge: REAL := 9.3;
    MinPressure: REAL := 1E-10;
    cDefaultPressure : REAL := 0;
END_VAR
(* 500 Logarithmic Output Conversion, factory default configuration *)

rV := 10*INT_TO_REAL(IG.i_iPRESS_R)/32767;

(* Set Guage State based on the Analog voltage*)
IF rV < vDisconnected THEN
    IG.eState := GaugeDisconnected;
    IG.rPRESS := cDefaultPressure;
ELSIF rV >= vDisconnected AND rV < (vNoDischarge -0.3)  THEN
    IG.eState := Valid;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
ELSIF rV >= (vNoDischarge -0.3) AND rV <(vGaugeOff -0.3)   THEN
    IG.eState := Starting;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
ELSIF rV >= (vGaugeOff -0.3) THEN
    IG.eState := Off;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
ELSE
    IG.eState := OoR;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
END_IF


(* Ion Gauge Protection Functions *)
(* If the PG pressure is greater than the VG.PRO_SP then the gauge is disabled *)
(* If the PG pressure is less than the VG.PRO_SP then the gauge is enabled *)
(* This FB also implements some hysteresis so the gauge doesn't have rapid power cycling while near the turn on boundary *)


IF PG.rPRESS <= IG.rPRO_SP AND PG.xPRESS_OK THEN

    IG.q_xHV_DIS := IG.xHV_SW;
    IG.xILKOk := TRUE;
ELSIF IG.q_xHV_DIS THEN
    IF IG.rPRESS > (IG.rPRO_SP + IG.rHYS_PR)  OR PG.rPRESS > (IG.rPRO_SP + IG.rHYS_PR) THEN
            IG.q_xHV_DIS := FALSE;
            IG.xHV_SW := FALSE;
    END_IF
ELSE
    IG.q_xHV_DIS := FALSE;
    IG.xHV_SW := FALSE;
    IG.xILKOk := FALSE;
END_IF

(* Pressure gauge OK checks *)

GaugeTurnOnTmr(IN:=IG.q_xHV_DIS, PT:=T#10S, Q=>IG.xTurnOnTime);


//Backwards compatibility
IG.xPRESS_OK := (IG.eState = Valid) OR IG.xBAKEOUT;

(* Setpoint evaluation *)
IG.xAT_VAC := (IG.xPRESS_OK AND (IG.rPRESS < IG.rVAC_SP)) OR IG.xBAKEOUT AND (IG.eState = Valid) ;

ACT_Logger();
(*Soft IO Linking*)
// check ethercat Diagnostics
IO();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
IF (IG.xLog) THEN
// ILK logger
IF NOT IG.xILKOk AND NOT IG.q_xHV_DIS THEN
            fbLogger(sMsg:='Lost external interlock while gauge was on.', eSevr:=TcEventSeverity.Critical);
END_IF


// Log Action
tAction(CLK:=  IG.q_xHV_DIS);
IF tAction.Q THEN fbLogger(sMsg:='Ion gauge commanded to switch on', eSevr:=TcEventSeverity.Info); END_IF



//STATE Logger
IF ePrevState <> IG.eState THEN
      CASE IG.eState OF
            ValidHi:
                    fbLogger(sMsg:='Gauge pressure valid high.', eSevr:=TcEventSeverity.Info);
            ValidLo:
                    fbLogger(sMsg:='Gauge pressure valid low.', eSevr:=TcEventSeverity.Info);
            Valid:
                    fbLogger(sMsg:='Gauge pressure valid.', eSevr:=TcEventSeverity.Info);
            GaugeDisconnected:
                    fbLogger(sMsg:='Gauge Disconnected.', eSevr:=TcEventSeverity.Critical);
            PressInvalid:
                    fbLogger(sMsg:='Gauge pressure invalid.', eSevr:=TcEventSeverity.Warning);
            OoR:
                    fbLogger(sMsg:='Gauge pressure out of range.', eSevr:=TcEventSeverity.Warning);
            Starting:
                    fbLogger(sMsg:='Gauge starting.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := IG.eState;
  END_IF
END_IF
END_ACTION
ACTION IO:
(*soft link inputs*)
IG.i_iPRESS_R:=     i_iPRESS_R;
(*soft link outputs*)
q_xHV_DIS := IG.q_xHV_DIS;

IG.sPath := sPath;
END_ACTION

FB_MKS722

(* This function is for the MKS 722*)

FUNCTION_BLOCK FB_MKS722
VAR_IN_OUT

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    VG      :       ST_VG;
END_VAR
VAR_INPUT
    i_rMinPressure  :       REAL;
    i_rMaxPressure  :       REAL;
END_VAR
VAR
    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
    rMinPressure: REAL;
    rVMin   :       REAL := 0.01; // Anything less than this voltage is considered disconnected
    rVMax : REAL := 10.5; // Anything more than this is considered invalid
    rV      :REAL;

    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
END_VAR
(* Real-value calculation *)
If (iTermBits=0) THEN iTermBits := 32767;END_IF
rV := 10*INT_TO_REAL(VG.i_iPRESS_R)/iTermBits;


// Is the gauge plugged in, and within a reasonable range?
IF rV < rVMin THEN
    VG.eState := GaugeDisconnected;
ELSIF rV > rVMax THEN
    VG.eState := PressInvalid;
ELSIF VG.rPRESS >= rMinPressure AND VG.rPRESS <= i_rMaxPressure THEN
                            VG.eState := Valid;
ELSIF VG.rPRESS < i_rMinPressure THEN
                            VG.eState := ValidLo;
ELSE
                            VG.eState := ValidHi;
END_IF

// Legacy Press OK boolean eval
VG.xPRESS_OK := VG.eState >= gc_GaugeValidState;

(*IO Soft Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
VG.i_iPRESS_R :=i_iPRESS_R;
END_ACTION

FB_MKS909

(* Standard MKS 909 hot cathode *)
(* works for 972 and 925 *)

FUNCTION_BLOCK FB_MKS909 EXTENDS FB_GaugeBase

VAR_INPUT
    PG      : ST_VG;
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    IG      : ST_VG;
END_VAR
VAR_IN_OUT

END_VAR
VAR
    rV : REAL;
    rMaxPressure    :       REAL := 5E-2; //Torr
    rMinPressure    :       REAL := 1E-10; //Torr
    timer:TON;
    (*Default set point 50 mT*)
    rVAC_SP: REAL := 9E-3; // as per manual
    iTermBits: UINT := 30518 ; // The terminal's maximum value in bits default el3174 as per vacuum architecture
    (*IO*)
    i_iPRESS_R AT%I* :INT; // input Pressure // Link to analog Input
    q_xHV_DIS AT %Q* : BOOL := True; // Active Low

END_VAR
(* Standard MKS 9XX series conversion *)
(* works for 972 and 925 *)

//Default setpoint 50 mT
IF PG.rVAC_SP = 0 THEN
    PG.rVAC_SP := rVAC_SP;
END_IF
// check no div by zero
If (iTermBits=0) THEN iTermBits := 30518;END_IF
rV := 10*INT_TO_REAL(PG.i_iPRESS_R)/iTermBits;

(* Pressure OK check *)
// Gauge state
// Is the gauge plugged in, and within a reasonable range?
IF  (rV >= 0.1 and rV <=8.7) THEN // as per manual
    IG.rPRESS := LREAL_TO_REAL(EXPT(10, rV-10));
    IG.eState := Valid;
    IG.xPRESS_OK := TRUE; //Backwards compatibility
ELSIF rV > 8.7 AND rV < 10 THEN
    IG.rPRESS :=rMaxPressure;
    IG.xPRESS_OK := FALSE;
    IG.eState := Off;
ELSE
    IG.xPRESS_OK := FALSE;
    IG.eState := GaugeDisconnected;
END_IF


IF (PG.rPRESS <= IG.rPRO_SP)  AND (PG.xPRESS_OK) THEN
             PG.q_xHV_DIS := NOT IG.xHV_SW;
             IG.xILKOk := TRUE;
    ELSIF NOT IG.q_xHV_DIS AND timer.Q THEN
            IF IG.rPRESS > (IG.rPRO_SP + IG.rHYS_PR) OR PG.rPRESS > (IG.rPRO_SP + IG.rHYS_PR) THEN
            IG.q_xHV_DIS := TRUE;
            IG.xHV_SW := FALSE;
            END_IF
    ELSE
            IG.q_xHV_DIS := TRUE;
            IG.xHV_SW := FALSE;
            IG.xILKOk := FALSE;
    END_IF


timer(IN:= NOT IG.q_xHV_DIS, PT:= T#2s);

(* Setpoint evaluation *)
IG.xAT_VAC := IG.xPRESS_OK AND (IG.rPRESS < IG.rVAC_SP);

(*Soft IO Mapping*)
ACT_IO();

(*Logger*)

THIS^.ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*soft link inputs*)
IG.i_iPRESS_R:=     i_iPRESS_R;

(*soft link outputs*)
q_xHV_DIS := IG.q_xHV_DIS;
END_ACTION
ACTION ACT_Logger:
// ILK logger
IF NOT IG.xILKOk AND NOT IG.q_xHV_DIS THEN
            fbLogger(sMsg:='Lost external interlock while gauge was on.', eSevr:=TcEventSeverity.Critical);
END_IF


// Log Action
tAction(CLK:=  IG.q_xHV_DIS);
IF tAction.Q THEN fbLogger(sMsg:='Ion gauge commanded to switch on', eSevr:=TcEventSeverity.Info); END_IF



//STATE Logger
IF ePrevState <> IG.eState THEN
      CASE IG.eState OF
            ValidHi:
            ValidLo:
            Valid:
                    fbLogger(sMsg:='Gauge pressure valid.', eSevr:=TcEventSeverity.Info);
            GaugeDisconnected:
                    fbLogger(sMsg:='Gauge Disconnected.', eSevr:=TcEventSeverity.Critical);
            PressInvalid:
                    fbLogger(sMsg:='Gauge pressure invalid.', eSevr:=TcEventSeverity.Warning);
            OoR:
                    fbLogger(sMsg:='Gauge pressure out of range.', eSevr:=TcEventSeverity.Warning);
            Starting:
                    fbLogger(sMsg:='Gauge starting.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := IG.eState;
  END_IF
END_ACTION

FB_MKS_937A

(* 937A Logarithmic Output Conversion *)
(* 7-15-2015 *)
(* This function will read the pressure from any gauge on a 937A. *)
(* To be used when the specific device function block is not available *)
FUNCTION_BLOCK FB_MKS_937A
VAR_IN_OUT

END_VAR
VAR_INPUT
    PG      :       ST_VG := g_DummyVG; // Gauge Structure used to Interlock the ion gauges
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv: '}
    VG : ST_VG; // Vacuum Gauge Data Structure
END_VAR
VAR
    rV      :       REAL;
            (*IOs to be linked*)
    /// Controls and I/Os
    i_iPRESS_R AT %I* :INT; // input Pressure // Link to analog Input
    q_xHV_DIS AT %Q* : BOOL; // Disable Gauge High Voltage when True // 'TcLinkTo' (EL2794) ^Output

END_VAR
(* 937A Logarithmic Output Conversion *)
(* 7-15-2015 *)
(* This function will read the pressure from any gauge on a 937A. *)

rV := 10*INT_TO_REAL(VG.i_iPRESS_R)/32767;
VG.rPRESS := LREAL_TO_REAL(EXPT(10,rV/0.6-12));


(* Ion Gauge Protection Functions *)
(* If the PG pressure is greater than the VG.PRO_SP then the gauge is disabled *)
(* If the PG pressure is less than the VG.PRO_SP then the gauge is enabled *)
(* This FB also implements some hysteresis so the gauge doesn't have rapid power cycling while near the turn on boundary *)

IF PG.rPRESS < VG.rPRO_SP THEN

VG.q_xHV_DIS :=  NOT VG.xHV_SW;

ELSIF NOT VG.q_xHV_DIS THEN
    IF VG.rPRESS > VG.rPRO_SP + VG.rHYS_PR OR PG.rPRESS > VG.rPRO_SP + VG.rHYS_PR THEN
            VG.q_xHV_DIS := TRUE;
    END_IF
ELSE
    VG.q_xHV_DIS := TRUE;
END_IF


(* Pressure gauge OK checks *)
VG.xPRESS_OK := (rV <=9.6 ) AND (rV>=0.6); //legacy

IF (rV <=9.6 ) AND (rV>=0.6) THEN
    VG.eState := Valid; // normal
ELSIF rV >= 0.18 AND rV <= 0.22 THEN
    VG.eState := ValidLo; //LO
ELSIF rV >= 9.7 AND rV<= 9.9 THEN
    VG.eState := ValidHi; //HIGH
ELSIF rV < 0.18 THEN
    VG.eState := GaugeDisconnected; //not on
ELSE
    VG.eState := PressInvalid; //other fault - could be no gauge, controller powering up etc
END_IF

(* Setpoint evaluation *)
VG.xAT_VAC := (VG.eState =Valid) AND VG.rPRESS < VG.rVAC_SP;

(*Soft IO Linking*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*soft link inputs*)
VG.i_iPRESS_R:=     i_iPRESS_R;

(*soft link outputs*)
q_xHV_DIS := VG.q_xHV_DIS;
END_ACTION

FB_MKS_937B

(* 937B Logarithmic Output Conversion *)
(* 5-20-2016, Alex Wallace and Scott Stubbs *)
(* This function will read the pressure from any gauge on a 937B. *)
(* To be used when the specific device function block is not available *)
FUNCTION_BLOCK FB_MKS_937B
VAR_IN_OUT

END_VAR
VAR_INPUT
    PG      :       ST_VG := g_DummyVG; // Gauge Structure used to Interlock the ion gauges
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv: '}
    VG : ST_VG; // Vacuum Gauge Data Structure



END_VAR
VAR
    rV      :       REAL;


            (*IOs to be linked*)
    /// Controls and I/Os
    i_iPRESS_R AT %I* :INT; // input Pressure // Link to analog Input
    q_xHV_DIS AT %Q* : BOOL; // Disable Gauge High Voltage when True // 'TcLinkTo' (EL2794) ^Output

END_VAR
(* 937B Logarithmic Output Conversion *)
(* 5-20-2016, Alex Wallace and Scott Stubbs *)
(* This function will read the pressure from any gauge on a 937B. *)

rV := 10*INT_TO_REAL(VG.i_iPRESS_R)/32767;
VG.rPRESS := LREAL_TO_REAL(EXPT(10,(rV-7.2)/0.6));


(* Gauge Protection Functions *)
(* If the PG pressure is greater than the VG.PRO_SP then the gauge is disabled *)
(* If the PG pressure is less than the VG.PRO_SP then the gauge is enabled *)
(* This FB also implements some hysteresis so the gauge doesn't have rapid power cycling while near the turn on boundary *)

IF PG.rPRESS < VG.rPRO_SP THEN // might enable the gauge automatically, do we want that?

VG.q_xHV_DIS := NOT VG.xHV_SW;

ELSIF NOT VG.q_xHV_DIS THEN
    IF VG.rPRESS > VG.rPRO_SP + VG.rHYS_PR OR PG.rPRESS > VG.rPRO_SP + VG.rHYS_PR THEN
            VG.q_xHV_DIS := TRUE;
    END_IF
ELSE
    VG.q_xHV_DIS := TRUE;
END_IF


(* Pressure gauge OK checks *)
VG.xPRESS_OK := (rV <=9.6 ) AND (rV>=0.6);
(* Pressure gauge State checks *)
IF (rV <=9.7 ) AND (rV>=0.6) THEN
    VG.eState := Valid; // normal
ELSIF rV >= 0.18 AND rV <= 0.22 THEN
    VG.eState := ValidLo; //LO
ELSIF rV > 9.7 AND rV<= 9.9 THEN
    VG.eState := ValidHi; //HIGH
ELSIF rV < 0.18 THEN
    VG.eState := GaugeDisconnected; //not on
ELSE
    VG.eState := PressInvalid; //other fault - could be no gauge, controller powering up etc
END_IF

(* Setpoint evaluation *)
VG.xAT_VAC := (VG.eState =Valid) AND VG.rPRESS < VG.rVAC_SP;


(*Soft IO Linking*)
// check ethercat Diagnostics
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*soft link inputs*)
VG.i_iPRESS_R:=     i_iPRESS_R;

(*soft link outputs*)
q_xHV_DIS := VG.q_xHV_DIS;
END_ACTION

FB_PIP_Gamma

(* This function block does basic controls FOR the ION pump connected to a Gamma QPCe controller.
 Provides interlocking interface. Enable HV only when interlock gauge press is less than 1.0E-4 Torr *)
{attribute 'reflection'}
FUNCTION_BLOCK FB_PIP_Gamma Extends FB_Pump
VAR_INPUT
    i_stGauge       :       ST_VG; //Ion or Pirani gauge for pump interlock
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum interlock logic*)
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    stPump  :       ST_PIP; //Gamma Ion pump structure
    q_IG    :       ST_VG; //When ion pump is used as a measuring device for interlocking gate valves
END_VAR
VAR_IN_OUT
END_VAR
VAR
 rPRESS : REAL;

 rV : REAL;
 timer:TON;
 rHVEna_SP : REAL :=1E-4; // Default protection setpoint as per the gamma QPCe manual
 (* IO Controls *)
 q_xHVEna_DO AT %Q*: BOOL;  // Enable High Voltage when TRUE
 i_iPRESS  AT %I*: INT;  //
 i_xSP_DI  AT %I* : BOOL; // NO contact //function of relay set on the QPC to HV output state

// For logging
tTimeOutAction : F_TRIG;
tOverrideActivated : R_TRIG;

tPumpStartTimeout : TON := (PT:=T#10s); // Timeout pump start if pressure < 1E-11 for more than 10s.
MinPressure : REAL:= 1E-11; // Minimum readback pressure, pump must register pressure above this to be considered running

//Overrides
    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tOvrd   :       TIME := T#10s;

    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
END_VAR
(* Does Gamma Ion pump HV interlock. Enable HV only when interlock gauge press is less than 1.0E-4 Torr *)

(*Ensures the set point is not higher than 1.0E-4*)
IF (stPump.rHVEna_SP > rHVEna_SP) THEN
    stPump.rHVEna_SP := rHVEna_SP;
END_IF

(* Interlock *)
//stPump.xHV_ExtIlk  := i_stGauge.xPRESS_OK AND (i_stGauge.rPRESS <= stPump.rHVEna_SP);//AND NOT tPumpStartTimeout.Q); //ADP

(* Enable HV *)
IF i_stGauge.rPRESS <= stPump.rHVEna_SP AND i_stGauge.xPRESS_OK THEN
    IF (stPump.xAutoOn) AND NOT (stPump.q_xHVEna_DO) THEN  stPump.q_xHVEna_DO := TRUE;
    ELSE stPump.q_xHVEna_DO := stPump.xHVEna_SW OR ((tonOvrd.Q AND i_xOverrideMode));
    END_IF
    stPump.xILKOk:= TRUE;
ELSIF stPump.q_xHVEna_DO (*AND timer.Q*) THEN
    IF q_IG.rPRESS > (stPump.rHVEna_SP + stPump.rHYS_PR) THEN // Ion pumps when running switches off based on their own pressure readings
            stPump.q_xHVEna_DO := FALSE;
            stPump.xHVEna_SW := FALSE;
            stPump.xILKOk := FALSE;
    ELSE
            stPump.q_xHVEna_DO := stPump.xHVEna_SW OR ((tonOvrd.Q AND i_xOverrideMode));
    END_IF
ELSIF (tonOvrd.Q AND i_xOverrideMode) THEN
    stPump.q_xHVEna_DO := TRUE;
ELSE
    stPump.q_xHVEna_DO  := FALSE;
    stPump.xHVEna_SW := FALSE;
    stPump.xILKOk := FALSE;
END_IF


//stPump.q_xHVEna_DO := stPump.xHVEna_SW AND stPump.xHV_ExtIlk;
//stPump.xHVEna_SW := stPump.q_xHVEna_DO;

timer(IN:= NOT stPump.q_xHVEna_DO, PT:= T#1s);

// TAW Experimental ion pump timeout
tPumpStartTimeout(IN:=(NOT stPump.i_xHV_DI AND stPump.q_xHVEna_DO));
tTimeOutAction(CLK:=tPumpStartTimeout.Q);
IF tTimeOutAction.Q THEN  fbLogger(sMsg:='Pump time out.', eSevr:=TcEventSeverity.Critical); END_IF



(*Pump STATE*)
IF tPumpStartTimeout.Q THEN //Pump timeout
    stPump.eState := pumpFAULT;
ELSIF stPump.q_xHVEna_DO AND NOT stPump.i_xHV_DI THEN
    stPump.eState := pumpSTARTING;
ELSIF NOT stPump.q_xHVEna_DO AND NOT stPump.i_xHV_DI THEN
    stPump.eState := pumpSTOPPED;
ELSIF stPump.q_xHVEna_DO AND stPump.i_xHV_DI THEN
    stPump.eState := pumpRUNNING;
ELSIF NOT stPump.q_xHVEna_DO AND stPump.i_xHV_DI THEN
    stPump.eState := pumpSTOPPING;
ELSE
    stPump.eState := pumpFAULT;
END_IF



(*Update output gauge values*)
ACT_SetGauge();
(*Soft IO Mapping*)
IO();
ACT_IlkOverride();
// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IlkOverride:
(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the Pump permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=stPump.xILKOk, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND stPump.pv_xOvrdStart THEN
    stPump.pv_xOvrdStart :=FALSE;
    if (stPump.eState = pumpRUNNING) AND (i_xOverrideMode) THEN stPump.xHVEna_SW:= TRUE; END_IF
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF
// Release the Force DO bit when the system override is false
IF NOT(i_xOverrideMode) THEN stPump.pv_xOvrdStart :=FALSE; END_IF
//Override timer
tonOvrd(IN:= stPump.pv_xOvrdStart, PT:=tOvrd);
END_ACTION
ACTION ACT_Logger:
// ILK logger

IF NOT (stPump.xILKOk) AND ((ePrevState = pumpRUNNING) OR (ePrevState = pumpSTARTING)) AND NOT tonOvrd.Q THEN
    fbLogger(sMsg:='Pump turned off due to loss of interlock.', eSevr:=TcEventSeverity.Critical);
END_IF

//STATE Logger
IF ePrevState <> stPump.eState THEN
      CASE stPump.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
            pumpSTOPPING:
                    fbLogger(sMsg:='Pump stopping.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := stPump.eState;
  END_IF


  // Log Action
tAction(CLK:=  stPump.q_xHVEna_DO);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF
// Log override mode enabled
tOverrideActivated(CLK:= (tonOvrd.Q AND i_xOverrideMode));
IF tOverrideActivated.Q THEN fbLogger(sMsg:='Pump override mode activated', eSevr:=TcEventSeverity.Warning); END_IF


// Log FAULT
tFault(CLK:=  NOT tPumpStartTimeout.Q);
IF tFault.Q THEN fbLogger(sMsg:='Pump Fault', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION
ACTION ACT_SetGauge:
(*MG*)
(* convert the analog input into readable pressure*)
(* use logarithmic Pressure calculation, Output= Normal and Offset = 0*)
rV := 10*INT_TO_REAL(i_iPress)/32767;
//Manual Page 9

if (stPump.bOutputInverted) THEN
    rPRESS := LREAL_TO_REAL(EXPT(10,(rV-stPump.iOffset)*-1));
ELSE
    rPRESS := LREAL_TO_REAL(EXPT(10,(rV-stPump.iOffset)));
END_IF
IF (stPump.q_xHVEna_DO) THEN
    q_IG.rPRESS := rPRESS;
    stPump.rPRESS := rPRESS;
END_IF


IF (stPump.i_xHV_DI) AND (q_IG.rPRESS < q_IG.rVAC_SP) THEN //ADP// AND (q_IG.rPRESS<>0) AND stPump.q_xHVEna_DO) THEN
    q_IG.xPRESS_OK := TRUE;
    ELSE
    q_IG.xPRESS_OK := FALSE;
END_IF

q_IG.sPath := This^.stPump.sPath;
END_ACTION
ACTION IO:
stPump.sIlkDeviceName := This^.i_stGauge.sPath;
stPump.xError := (stPump.eState = pumpFAULT);
stPump.xOverrideMode := i_xOverrideMode;
(*inputs*)
stPump.i_iPRESS:=   i_iPRESS;
stPump.i_xHV_DI:=   i_xSP_DI;
(*output*)
q_xHVEna_DO:=  stPump.q_xHVEna_DO; //According to Manual pin should be grounded to activate function

 This^.stPump.sPath:= sPath;
END_ACTION

FB_PIP_Interface_In

(* This function block is created for interface devices between different PLC*)
(* Not all the Variables in the original structure is required, just few signals *)
(* They have to be linked to the custom created variables on the EL6692/5 primary side*)
FUNCTION_BLOCK FB_PIP_Interface_In
VAR_INPUT
END_VAR
VAR_OUTPUT
    VG : ST_VG;
END_VAR
VAR
    i_rPRESS AT%I*: REAL;  //This is the human-readable pressure
    i_xAT_VAC AT%I*: BOOL;
    i_xPRESS_OK AT%I*: BOOL;
    i_xIs_Connected AT%I*: BOOL;
END_VAR
(* This function block is created for interface devices between different PLC*)
(* Not all the Variables in the original structure is required, just few signals *)
(* They have to be linked to the custom created variables on the EL6692/5 primary side*)

VG.rPRESS := i_rPRESS;
VG.xAT_VAC := i_xAT_VAC;
VG.xPRESS_OK := i_xPRESS_OK;


IF NOT(i_xIs_Connected) THEN
    i_xPRESS_OK := FALSE;
END_IF

END_FUNCTION_BLOCK

FB_PIP_Interface_Out

(* This function block is created for interface devices between different PLC*)
(* Not all the Variables in the original structure is required, just few signals *)
(* They have to be linked to the custom created variables on the EL6692/5 primary side*)
FUNCTION_BLOCK FB_PIP_Interface_Out
VAR_INPUT
    VG : ST_VG;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    i_rPRESS AT%Q*: REAL;
    i_xAT_VAC AT%Q*: BOOL;
    i_xPRESS_OK AT%Q*: BOOL;
END_VAR
(* This function block is created for interface devices between different PLC*)
(* Not all the Variables in the original structure is required, just few signals *)
(* They have to be linked to the custom created variables on the EL6692/5 primary side*)

i_rPRESS:=VG.rPRESS;
i_xAT_VAC:= VG.xAT_VAC;
i_xPRESS_OK:=VG.xPRESS_OK;

END_FUNCTION_BLOCK

FB_PIP_Test

FUNCTION_BLOCK FB_PIP_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    PG : ST_VG;
    fb_PIP_GAMMA: FB_PIP_GAMMA;
END_VAR
M_INIT();
M_Interlock();
M_AutoStart();

END_FUNCTION_BLOCK

FB_PressureState

(*Deprecated*)
FUNCTION_BLOCK FB_PressureState
VAR_INPUT
    i_rV    :       REAL;
    i_rVMin :       REAL := 0.01; // Anything less than this voltage is considered disconnected
    i_rVMax :       REAL := 10.5; // Anything more than this is considered invalid
    i_IGTurnOnTime  :       TIME := T#10S;

    i_MinPressure   :       REAL;
    i_MaxPressure   :       REAL;
END_VAR
VAR_OUTPUT
    q_eState        :       E_PressureState;
END_VAR
VAR_IN_OUT
    iq_stVG :       ST_VG;
END_VAR
VAR
    tonGaugeTurnOnTmr : TON;
END_VAR
(* Pressure State Evaluation
A. Wallace, 2016-8-31

This FB serves to evaluate the state of the pressure based on the raw voltage, pressure, and gauge state.

*)
// Is the gauge plugged in, and within a reasonable range?
IF i_rV < i_rVMin THEN
    q_eState := GaugeDisconnected;
ELSIF i_rV > i_rVMax THEN
    q_eState := PressInvalid;
ELSE
    // Is the gauge reading back a pressure that makes sense?
    CASE iq_stVG.eTYPE OF
            IG903:
                    tonGaugeTurnOnTmr(IN:=iq_stVG.q_xHV_DIS, PT:=i_IGTurnOnTime, Q=>iq_stVG.xTurnOnTime);
                    IF iq_stVG.q_xHV_DIS THEN
                            IF tonGaugeTurnOnTmr.Q THEN
                                    IF iq_stVG.rPRESS >= i_MinPressure AND iq_stVG.rPRESS <= i_MaxPressure THEN
                                            q_eState := Valid;
                                    ELSE
                                            q_eState := OoR;
                                    END_IF
                            ELSE
                                            q_eState := Starting;
                            END_IF
                    ELSE
                            q_eState := Off;
                    END_IF
            IG909:
                    IF iq_stVG.q_xHV_DIS THEN
                            IF iq_stVG.rPRESS >= i_MinPressure AND iq_stVG.rPRESS <= i_MaxPressure THEN
                                    q_eState := Valid;
                            ELSE
                                    q_eState := OoR;
                            END_IF
                    ELSE
                            q_eState := Off;
                    END_IF
            PG907, PG925, PG722B:
                    IF iq_stVG.rPRESS >= i_MinPressure AND iq_stVG.rPRESS <= i_MaxPressure THEN
                            q_eState := Valid;
                    ELSIF iq_stVG.rPRESS < i_MinPressure THEN
                            q_eState := ValidLo;
                    ELSE
                            q_eState := ValidHi;
                    END_IF
            ELSE
                    q_eState := PressInvalid;
    END_CASE
END_IF


iq_stVG.eState := q_eState;

// Legacy Press OK boolean eval
iq_stVG.xPRESS_OK := q_eState >= gc_GaugeValidState;

END_FUNCTION_BLOCK

FB_PTM_Agilent

(* This function block does basic controls FOR the Agilent TV Turbo pump. Turns off pump
in the event of errors/ warnings. Provides interlocking interface.*)
FUNCTION_BLOCK FB_PTM_Agilent EXTENDS FB_Pump
VAR_IN_OUT

END_VAR
VAR_INPUT
    i_xExtILKOk : BOOL; // Connect to external interlock logic(e.g TURBO_ILK Function), TRUE if not used.
    i_stGauge       :       ST_VG; //Pirani backing gauge for pump interlock
    i_rMaxBackingPressure : REAL;
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' :=' pv:'}
    iq_stPtm        :       ST_AgilentPTM;

END_VAR
VAR
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
    //xRunOk        :       BOOL;
    nMaxR1Fault: INT :=5;
    nR1Fault : INT;
    tFaultWindowDuration : TIME := T#300S;
    tR1FaultDuration : TIME := T#120S;
    tTimeOutDuration: TIME:= T#10M;
    tFaultWindowElapsed: TON;
    tR1Fault : TON;
    tStartTimeOut : TON;
    tR1TimeOut: TON;
    step : INT :=0;
    nErrorCode: INT :=0;
    xBackingPressureOK: BOOL;
    iMaxSpeed : INT:=963;

    rPowerScale: REAL:=10;//W
    rCurrentScale: REAL:=1;//A

    (*I/Os*)
    i_xSTART        AT%I*:  BOOL;
    i_xR1   AT%I*   :  BOOL;
    i_xR2   AT%I*   :  BOOL;
    i_xLSpd AT%I*:  BOOL;
    i_iTempMon AT%I*: INT;
    i_iPowerMon AT%I*: INT;
    i_iCurrentMon AT%I*: INT;
    i_iRawSpeed AT %I* : INT;
    i_xFault AT%I*  :       BOOL;
    q_RunDO AT%Q*   :       BOOL;
    q_xLSpd AT%Q*   :  BOOL;


    // For logging
  //  Msg : FB_FormatString;
   // fbJson : FB_JsonSaxWriter;
   tOverrideActivated : R_TRIG;

    iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
END_VAR
(* Basic Agilent Turbo Controls *)
(* M. Ghaly, 2018-9-26 *)

(*connect External interlock to structure variable*)
//iq_stPtm.xExtRunOk := i_xExtILKOk;

(* state machine *)
CASE (step) OF
(*Pump Stopped State*)
    0: // Pump Stopped State
            iq_stPtm.xRunSW:=FALSE;
            //Reset Output signals;
            iq_stPtm.q_RunDO:=FALSE;
            iq_stPtm.q_xSS := FALSE;
            iq_stPtm.q_xLSpd:= FALSE;
            //Reset timer
            tFaultWindowElapsed.IN := FALSE;
            nR1Fault := 0;
            iq_stPtm.eState := pumpSTOPPED;
            step :=10;

(*Idle State waiting to start*)
    10: // Idle State, waiting for the xRunSwitch
            IF iq_stPtm.xExtRunOk  AND iq_stPTM.xRunSW THEN
                    iq_stPtm.q_RunDO := iq_stPTM.xRunSW; //
                    step:=20;
            END_IF
(*Pump Starting State*)
    20: // Pump is starting
            nErrorCode:=0;
            iq_stPtm.q_RunDO := iq_stPTM.xRunSW; // done in previous state
            IF (*(NOT iq_stPtm.i_xR1) AND*)iq_stPtm.i_xSTART  THEN
            iq_stPtm.eState := pumpSTARTING;
            step:= 40;
            ELSIF tStartTimeOut.Q THEN
                    step:=90; // or fault state
                    nErrorCode:=950;
            END_IF
            // should check R1 ?? check Fault condition?


    40: // Pump is starting
            iq_stPtm.eState := pumpSTARTING;
            IF   NOT iq_stPtm.i_xR1 AND NOT iq_stPtm.i_xSTART  THEN
                    step := 0 ;
            ELSIF NOT iq_stPtm.i_xR1 AND iq_stPtm.i_xSTART  THEN
                    step :=40;
            ELSIF iq_stPtm.i_xR1 AND NOT iq_stPtm.i_xSTART THEN
                    step:= 50;
            END_IF
            IF tR1TimeOut.Q THEN
                    step:= 90;
                    iq_stPtm.xPumpFaultLock := TRUE;
                    nErrorCode:= 930; // R1 Signal at Start time out.
           // fbLogger(sMsg:='Timeout: Pump could not achieve running speed.', eSevr:=TcEventSeverity.Warning);
            END_IF

    50: // Pump is Running (Normal Operation) ( R1 is True and STARTing is FALSE)
            iq_stPtm.eState := pumpRUNNING;
            IF NOT iq_stPtm.i_xR1 THEN
                    nR1Fault:= nR1Fault+1 ;
                    tFaultWindowElapsed.IN := TRUE; ///
                    //tR1Fault.IN := TRUE;
                    step :=60;
            END_IF

        //Log new state
        IF ePrevState <> iq_stPtm.eState THEN
            //fbLogger(sMsg:='Pump at speed.', eSevr:=TcEventSeverity.Verbose);
            ePrevState := iq_stPtm.eState;
            END_IF

    60: // Pump is Running (Not Nomral Operation) (R1 is False, pump not @ speed )
            IF ((nR1Fault = nMaxR1Fault) AND NOT tFaultWindowElapsed.Q) OR (tR1Fault.Q) THEN // number of fault occured during the same time window
                    step:= 90;
                    tR1Fault.IN := FALSE;
                    tFaultWindowElapsed.IN := FALSE;
                    iq_stPtm.xPumpFaultLock := TRUE;
                    nErrorCode:= 920; // R1 Signal Lost multiple times.
            //fbLogger(sMsg:='Running signal Lost multiple times.', eSevr:=TcEventSeverity.Warning);
            ELSIF tFaultWindowElapsed.Q  THEN
                    tFaultWindowElapsed.IN := FALSE;
                    nR1Fault := 0;
            ELSIF iq_stPtm.i_xR1 THEN
                    tR1Fault.IN := FALSE;
                    step:=50;
            END_IF

    90: // Pump Fault STATE
            iq_stPtm.xRunSW:=FALSE;
            iq_stPtm.q_RunDO:=FALSE;
            iq_stPtm.q_xSS := FALSE;
            iq_stPtm.q_xLSpd:= FALSE;
            iq_stPtm.eState := pumpFAULT;
            step := 95;

    95:
            IF (NOT iq_stPtm.xPumpFaultLock) OR iq_stPtm.xRunSW THEN
                    step:=0;
            END_IF
END_CASE

(* Interrupts *)
(* FAULT signal*)
IF (iq_stPtm.i_xFault) AND (step < 90 ) THEN
    iq_stPtm.eState := pumpFAULT; // no need taken care of in the state
    iq_stPtm.xPumpFaultLock := TRUE;
    nErrorCode := 900; // Pump Controller FAULT
    step:=90;
END_IF


(*Pump Lock Reset bit*)
IF (iq_stPtm.xResetSW ) THEN
    iq_stPtm.xResetSW := FALSE;
    iq_stPtm.xPumpFaultLock := FALSE;
END_IF

(*Check backing gauge pressure*)
//xBackingPressureOK := i_stGauge.xPRESS_OK AND ( i_stGauge.rPRESS < iq_stPtm.rBackingPressureSP);

(* Interlock Sum *)
iq_stPtm.xExtRunOk := i_xExtILKOk AND NOT iq_stPtm.i_xFault AND NOT iq_stPtm.xPumpFaultLock AND NOT(tILK.Q);

IF NOT iq_stPtm.xExtRunOk AND (NOT (step >= 90)) THEN
    step:=0;
END_IF

(*Stop By the Operator and not due to fault *)
IF (NOT iq_stPtm.xRunSW) AND (NOT (step >= 90)) AND (NOT (step = 10)) THEN
    step := 0;
END_IF

// Log faults Moved to ACT_Logger
(*tErrorPresent(CLK:=iq_stPtm.xPumpFaultLock);
IF tErrorPresent.Q THEN
    fbJson.StartObject();
    fbJson.AddKey('vacuum_typecode');
    fbJson.AddUdint(nErrorCode);
    fbJson.EndObject();
    fbLogger.sJson := fbJson.GetDocument();
    fbLogger(sMsg:='Turbo control fault', eSevr:=TcEventSeverity.Warning);
    fbJson.ResetDocument();
END_IF
*)

(* Timers *)
tFaultWindowElapsed(PT:=tFaultWindowDuration);
tR1Fault(IN:= (step = 60), PT:=tR1FaultDuration);
tStartTimeOut(IN := (step=20), PT:=tTimeOutDuration);
tR1TimeOut(IN := (step=40), PT:=tTimeOutDuration);
(* IO Mapping*)
IO();
(*Assign Error Message*)
iq_stPtm.sError := ErrorMessage(nErrorCode, iq_stPtm.eState);
// Log States and triggers
ACT_Logger();
(*Validate Backing Pressure set point doesn't exceed the Maximum backingPressure*)
iq_stPtm.rBackingPressureSP := BackingPressureSetPoint(iq_stPtm.rBackingPressureSP,i_rMaxBackingPressure);
ACT_Interlock();

END_FUNCTION_BLOCK
ACTION ACT_Interlock:
This^.tILK(IN:= (i_stGauge.rPRESS>=iq_stPtm.rBackingPressureSP), PT:=T#5S, Q=> );
END_ACTION
ACTION ACT_Logger:
//STATE Logger
// ILK logger
IF NOT i_xExtIlkOK AND ePrevState = pumpRUNNING THEN
            fbLogger(sMsg:='Lost external interlock while pump was running.', eSevr:=TcEventSeverity.Critical);
END_IF


IF ePrevState <> iq_stPTM.eState THEN
      CASE iq_stPTM.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stPTM.eState;
  END_IF

// Log Action
tAction(CLK:=  iq_stPTM.q_RunDO);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF


// Log FAULT
tFault(CLK:= iq_stPTM.i_xFault OR iq_stPtm.xPumpFaultLock);
IF tFault.Q THEN fbLogger(sMsg:=iq_stPtm.sError, eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION
ACTION IO:
(*inputs*)
iq_stPtm.i_xSTART:= i_xSTART;
iq_stPtm.i_xR1:=    i_xR1;
iq_stPtm.i_xR2:=    i_xR2;
iq_stPtm.i_xLSpd:=  i_xLSpd;
iq_stPtm.i_xFault:= i_xFault;
(* Real-value calculation *)
If (iTermBits=0) THEN iTermBits := 32767;END_IF
iq_stPtm.i_rCurrentMon := (10*INT_TO_REAL(i_iCurrentMon)/iTermBits)*rCurrentScale;
iq_stPtm.i_rTempMon := 10*INT_TO_REAL(i_iTempMon)/iTermBits;
iq_stPtm.i_rPowerMon := (10*INT_TO_REAL(i_iPowerMon)/iTermBits)*rPowerScale;
iq_stPtm.i_diCurSpd := 10*LREAL_TO_DINT(INT_TO_REAL(i_iRawSpeed)/iTermBits);


(*outputs*)
q_RunDO := iq_stPtm.q_RunDO;
q_xLSpd := iq_stPtm.q_xLSpd;
END_ACTION

FB_PTM_Ebara_010M

(* This function block does basic controls FOR the Ebara Turbo pump connected to the ETC010M Controller.
 Turns off pump in the event of errors/ warnings. Provides interlocking interface.*)
 {attribute 'no_check'}
FUNCTION_BLOCK FB_PTM_Ebara_010M EXTENDS FB_Pump
VAR_IN_OUT

END_VAR
VAR_INPUT
    i_xExtILKOk : BOOL; // Turbo ILK bit , set to True if not used
    //i_iRawSpeed: INT; //Should come from PLC Soft IO.
    i_rMaxBackingPressure : REAL := 3;
    i_iMinSpeed : DINT := 100; //Hz check pump manual for setting P43
    i_iMaxSpeed : DINT := 560; //Hz check pump manual for setting P43
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stPTM : ST_EbaraPTM;
END_VAR
VAR
    i_iADCBits : UINT := 15;
    RTRIG_INLK: R_TRIG;
    TOF_RESET: TON :=(PT:=T#1S);
    TOF_SetSpeed: TON :=(PT:=T#1S);

    (*IO*)
    (*inputs*)
    i_xDecel AT %I* : BOOL; //Link to brake input
    i_xAccel AT %I* : BOOL;
    i_xRotate AT %I* : BOOL;
    i_xNCFault AT %I* : BOOL;//
    i_xAtSpd AT %I* : BOOL;
    i_iRawSpeed AT %I* : INT; // Link to Analog input
    i_iTempMon AT %I* : INT; // Link to Analog input -- input Voltage according to the pump temprature 0->5V 0->100C
    i_iCurrentMon AT %I* : INT; //Link to Analog -- input Voltage to the output current of motor 0->5V 0->10A

    (*output*)
    q_xStart AT %Q* : BOOL; // link to output
    q_xStop AT %Q* : BOOL; //Link to output
    q_xReset AT %Q* : BOOL;
    q_xProtection AT %Q* : BOOL;
    q_xSetSpeed AT %Q* : BOOL;
    q_iSpeedSet AT %Q* :INT;        //Link to analog Output

END_VAR
(* Ebara ETC control FB
Scott Stubbs & Alex W.
Modified Nov 2018 by M. Ghaly
*)
(* The pump runs when the RunDO is set to true. The RunDO sets the outputs Start and Stop to true when it
is true, and false otherwise. Per the manual, when START bit is closed the pump will accelerate, and when the STOP
bit is Opened, the pump will decelerate with the brake and stop *)

(* Simple protection reset *)
iq_stPtm.xExtRunOk:= i_xExtILKOk AND NOT iq_stPTM.i_xFault;
iq_stPTM.q_xProtection:= TRUE;//iq_stPTM.xExtRunOk;
(*soft IO mapping*)
ACT_IO();

(* Basic pump supervisory section *)
    (* If override mode, ignore everything else *)
IF iq_stPTM.i_xOverride THEN
    IF iq_stPTM.xRunSW THEN
            iq_stPTM.q_RunDO:=TRUE;
    ELSE
            iq_stPTM.q_RunDO:=FALSE;
    END_IF
    //Handle faults
ELSIF iq_stPTM.i_xFault OR ( NOT iq_stPTM.xExtRunOk) THEN
    iq_stPTM.q_RunDO:=FALSE;
    iq_stPTM.xRunSW:=FALSE;
    //And one for when we need to start the pump normally. Only allows pump to be started if ILK ok.
ELSIF iq_stPTM.xRunSW AND iq_stPTM.xExtRunOk THEN
    iq_stPTM.q_RunDO:=TRUE;
END_IF
//One section for when pump is told to stop
IF NOT iq_stPTM.xRunSW THEN
            iq_stPTM.q_RunDO:=FALSE;
            //IF (NOT iq_stPTM.i_xFault)(* AND (NOT iq_stPTM.i_xALARM) *)THEN iq_stPTM.eState := pumpSTOPPED; END_IF;
END_IF

//Set the Start and Stop outputs to Run do . check manual alternate START/STOP
iq_stPTM.q_xStart := iq_stPTM.q_RunDO;
iq_stPTM.q_xStop := iq_stPTM.q_RunDO;



 (* to reset the Input speed OK bit *)
 TOF_SetSpeed (IN:= iq_stPTM.iq_xSpeedSet, Q => );
 IF  (TOF_SetSpeed.Q) THEN iq_stPTM.iq_xSpeedSet:= FALSE;
 END_IF
 (* to reset the Reset Bit *)
 IF (iq_stPTM.xResetSW ) THEN  iq_stPTM.q_xReset := TRUE;
 END_IF
 TOF_RESET (IN:= iq_stPTM.q_xReset, Q => );
 IF TOF_RESET.Q THEN
    iq_stPTM.q_xReset:= FALSE;
    iq_stPTM.xResetSW := FALSE;
 END_IF
 (*When the Protection signal is lost, Error bit doesn't show, but the controller still needs to be reset*)
RTRIG_INLK(CLK:= iq_stPTM.q_xProtection );
 IF (RTRIG_INLK.Q) AND (iq_stPTM.i_xNCFault) THEN
     iq_stPTM.q_xReset := TRUE;
 END_IF



(*Validate Backing Pressure set point doesn't exceed the Maximum backingPressure*)
iq_stPtm.rBackingPressureSP := BackingPressureSetPoint(iq_stPtm.rBackingPressureSP,i_rMaxBackingPressure);


 (*Pump STATE*)
IF iq_stPtm.i_xFault THEN
    iq_stPtm.eState := pumpFAULT;
ELSIF iq_stPTM.i_xDecel AND NOT iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpSTOPPING;
ELSIF NOT iq_stPtm.q_RunDO AND NOT iq_stPTM.i_xFault THEN
    iq_stPtm.eState := pumpSTOPPED;
ELSIF NOT iq_stPtm.i_xAtSpd  AND iq_stPtm.q_RunDO AND iq_stPTM.i_xAccel THEN
    iq_stPtm.eState := pumpSTARTING;
ELSIF iq_stPtm.i_xAtSpd AND iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpRUNNING;
ELSE
    iq_stPtm.eState := pumpFAULT;
END_IF

(*soft IO mapping*)
ACT_IO();
// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*soft io mapping*)
(*inputs*)
    iq_stPTM.i_xDecel := i_xDecel ;
    iq_stPTM.i_xAccel :=    i_xAccel ;
    iq_stPTM.i_xRotate :=   i_xRotate;
    iq_stPTM.i_xFault := NOT iq_stPTM.i_xNCFault;   //Normally closed fault handling
    iq_stPTM.i_xNCFault :=  i_xNCFault ;
    iq_stPTM.i_xALARM:= NOT i_xNCFault;
    iq_stPTM.i_xAtSpd := i_xAtSpd;
    iq_stPTM.i_rTempMon := (100*(INT_TO_REAL(i_iTempMon)/16383));// Manual page 28(DC 0 to 5V -> 0 to 100 DegC)
    iq_stPTM.i_rCurrentMon := (10*(INT_TO_REAL(i_iCurrentMon)/16383));// Manual page 28 (DC 0 to 5V -> 0 to 10 A)
    iq_stPTM.i_diCurSpd := LREAL_TO_DINT(560*LREAL_TO_DINT(i_iRawSpeed)/16383);//(560*(INT_TO_REAL(i_iRawSpeed)/16383));// Manual page 28 (DC 0 to 5V -> 100 to 560Hz Rated Rotational Speed )
(*outputs*)
    q_xStart:= iq_stPTM.q_xStart;
    q_xStop := iq_stPTM.q_xStop;
    q_xReset := iq_stPTM.q_xReset;
    q_xProtection := iq_stPTM.q_xProtection;
    q_xSetSpeed := iq_stPTM.iq_xSpeedSet;


(*Validate Set Speed within range *)
IF iq_stPTM.q_iSpeedSet  >= i_iMinSpeed AND iq_stPTM.q_iSpeedSet  <=i_iMaxSpeed THEN
    q_iSpeedSet  := LIMIT(0, DINT_TO_INT((iq_stPTM.q_iSpeedSet - i_iMinSpeed) / i_iMaxSpeed *16383/5),16383); //Max 5V
END_IF
END_ACTION
ACTION ACT_Logger:
IF NOT i_xExtIlkOK AND (ePrevState = pumpRUNNING OR ePrevState = pumpSTARTING) THEN
            fbLogger(sMsg:='Pump turned off due to loss of interlock.', eSevr:=TcEventSeverity.Critical);
END_IF
// Log Action
tAction(CLK:=  iq_stPTM.q_RunDO);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF



//STATE Logger
IF ePrevState <> iq_stPTM.eState THEN
      CASE iq_stPTM.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
            pumpSTOPPING:
                    fbLogger(sMsg:='Pump decelerating.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stPTM.eState;
  END_IF


// Log FAULT
tFault(CLK:= iq_stPTM.i_xNCFault);
IF tFault.Q THEN fbLogger(sMsg:='Pump Lost Alarm OK bit', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

FB_PTM_Ebara_011M

(* This function block does basic controls FOR the Ebara Turbo pump connected to the ETC011M Controller.
 Turns off pump in the event of errors/ warnings. Provides interlocking interface.*)
{attribute 'no_check'}
FUNCTION_BLOCK FB_PTM_Ebara_011M EXTENDS FB_Pump
VAR_IN_OUT
END_VAR
VAR_INPUT
    i_rMaxBackingPressure : REAL := 3;
    i_iMinSpeed : DINT := 100; //check pump manual for setting
    i_iMaxSpeed : DINT := 560; // check pump manual for setting
    i_xExtILKOk : BOOL; // Connect to external interlock logic, TRUE if not used.
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stPTM : ST_EbaraPTM;
END_VAR
VAR
    TOF_SetSpeed: TON := (PT:=T#1S);
    TOF_RESET: TON :=(PT:=T#1S);
    i_iADCBits : UINT := 12;
    (*IO*)
    (*inputs*)
    i_xDecel AT %I* : BOOL; //Link to brake input
    i_xAccel AT %I* : BOOL;
    i_xRotate AT %I* : BOOL;
    i_xFaultNC AT %I* : BOOL;// remove?
    i_xAtSpd AT %I* : BOOL;
    i_iRawSpeed AT %I* : INT; // Link to Analog input

    (*output*)
    q_xStart AT %Q* : BOOL; // link to output
    q_xStop AT %Q* : BOOL; //Link to output
    q_xReset AT %Q* : BOOL;
    q_xProtection AT %Q* : BOOL;
    q_xSetSpeed AT %Q* : BOOL;
    q_iSpeedSet AT %Q* :INT;        //Link to analog Output


END_VAR
(* Ebara ETC control FB
Scott Stubbs & Alex W.
Modified Nov 2018 by M. Ghaly
*)

(* Switch processing *)
//TOF_START.IN := iq_stPTM.i_xStart;
//TOF_START();
//TOF_STOP.IN  := iq_stPTM.i_xStop;
//TOF_STOP();
//TOF_RESET.IN := iq_stPTM.i_xReset;
//TOF_RESET();

(* Simple protection reset *)
iq_stPtm.xExtRunOk:= i_xExtILKOk;
iq_stPTM.q_xProtection := TRUE;//iq_stPTM.xExtRunOk;

(*soft IO mapping*)
ACT_IO();

(* Basic pump supervisory section *)
    (* If override mode, ignore everything else *)
IF iq_stPTM.i_xOverride THEN
    IF iq_stPTM.xRunSW THEN
            iq_stPTM.q_RunDO:=TRUE;
    ELSE
            iq_stPTM.q_RunDO:=FALSE;
    END_IF
    //Handle faults - the way the Ebara cable is done, xFault is normally closed
ELSIF iq_stPTM.i_xFault OR (iq_stPTM.q_RunDO AND NOT iq_stPTM.xExtRunOk) THEN
    iq_stPTM.q_RunDO:=FALSE;
    //iq_stPTM.q_xProtection:=FALSE;
    iq_stPTM.xRunSW:=FALSE;
    //And one for when we need to start the pump normally. Only allows pump to be started if ILK ok.
ELSIF iq_stPTM.xRunSW AND iq_stPTM.xExtRunOk THEN
    iq_stPTM.q_RunDO:=TRUE;
END_IF
//One section for when pump is told to stop
IF NOT iq_stPTM.xRunSW THEN
            iq_stPTM.q_RunDO:=FALSE;
            IF (NOT iq_stPTM.i_xFault) AND (NOT iq_stPTM.i_xALARM) THEN iq_stPTM.eState := pumpSTOPPED; END_IF;
END_IF

//Set the Start and Stop outputs to Run do . check manual.
iq_stPTM.q_xStart := iq_stPTM.q_xStop := iq_stPTM.q_RunDO;

(* to reset the Input speed OK bit *)
 TOF_SetSpeed (IN:= iq_stPTM.iq_xSpeedSet, Q => );
 IF TOF_SetSpeed.Q THEN iq_stPTM.iq_xSpeedSet:= FALSE;
 END_IF
 (* to reset the Reset Bit *)
 IF (iq_stPTM.xResetSW ) THEN  iq_stPTM.q_xReset := TRUE;
 END_IF
 TOF_RESET (IN:= iq_stPTM.q_xReset, Q => );
 IF TOF_RESET.Q THEN
    iq_stPTM.q_xReset:= FALSE;
    iq_stPTM.xResetSW := FALSE;
 END_IF


 (*Pump STATE*)
IF iq_stPtm.i_xFault THEN
    iq_stPtm.eState := pumpFAULT;
ELSIF iq_stPTM.i_xDecel AND NOT iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpSTOPPING;
ELSIF NOT iq_stPtm.q_RunDO AND NOT iq_stPTM.i_xFault THEN
    iq_stPtm.eState := pumpSTOPPED;
ELSIF NOT iq_stPtm.i_xAtSpd  AND iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpSTARTING;
ELSIF iq_stPtm.i_xAtSpd AND iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpRUNNING;
ELSE
    iq_stPtm.eState := pumpFAULT;
END_IF

(*Validate Backing Pressure set point doesn't exceed the Maximum backingPressure*)
iq_stPtm.rBackingPressureSP := BackingPressureSetPoint(iq_stPtm.rBackingPressureSP,i_rMaxBackingPressure);


(*soft IO mapping*)
ACT_IO();
// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*soft io mapping*)
(*inputs*)
    iq_stPTM.i_xDecel := i_xDecel ;
    iq_stPTM.i_xAccel :=    i_xAccel ;
    iq_stPTM.i_xRotate :=   i_xRotate;
    iq_stPTM.i_xNCFault :=  i_xFaultNC ;
    iq_stPTM.i_xAtSpd := i_xAtSpd;
    //iq_stPTM.i_iRawSpeed :=       i_iRawSpeed;
//V (AI/32767*10) * (33600 {Max RPM} - 100) / 5
(*outputs*)
    q_xStart:= iq_stPTM.q_xStart;
    q_xStop := iq_stPTM.q_xStop;
    q_xReset := iq_stPTM.q_xReset;
    q_xProtection := iq_stPTM.q_xProtection;
    q_xSetSpeed := iq_stPTM.iq_xSpeedSet;
(*Validate Set Speed within range *)
IF iq_stPTM.q_iSpeedSet  >= i_iMinSpeed AND iq_stPTM.q_iSpeedSet  <=i_iMaxSpeed THEN
    q_iSpeedSet  := LIMIT(0, DINT_TO_INT((iq_stPTM.q_iSpeedSet - i_iMinSpeed) / i_iMaxSpeed *16383/5),16383); //Max output 5V
    //:= DINT_TO_INT((iq_stPTM.q_iSpeedSet-100) / 33600 *32767/2); //Max 5V
END_IF

(* Pump speed calculation *)
IF i_iRawSpeed / (EXPT(2,i_iADCBits)-1) * 10 >= 0.2 THEN //Speed reading appears zero at ~0.16V
iq_stPTM.i_diCurSpd := LREAL_TO_DINT(560*LREAL_TO_DINT(i_iRawSpeed)/16383);
//iq_stPTM.i_diCurSpd := LREAL_TO_DINT(i_iRawSpeed/(EXPT(2,i_iADCBits)-1)*67000);
//V (AI/(2^{# ADC bits} -1 ) * 10[ADC 10V scaling]) * (33600 [Max RPM] - 100) / 5
ELSE
    iq_stPTM.i_diCurSpd := 0;
END_IF

//Normally closed fault handling
iq_stPTM.i_xFault := NOT iq_stPTM.i_xNCFault; //??
END_ACTION
ACTION ACT_Logger:
//STATE Logger
IF NOT i_xExtIlkOK AND ePrevState = pumpRUNNING THEN
            fbLogger(sMsg:='Lost external interlock while pump was running.', eSevr:=TcEventSeverity.Critical);
END_IF


IF ePrevState <> iq_stPTM.eState THEN
      CASE iq_stPTM.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
            pumpSTOPPING:
                    fbLogger(sMsg:='Pump decelerating.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stPTM.eState;
  END_IF

// ILK logger



// Log Action
tAction(CLK:=  iq_stPTM.q_RunDO);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF


// Log FAULT
tFault(CLK:= iq_stPTM.i_xNCFault);
IF tFault.Q THEN fbLogger(sMsg:='Pump Lost Alarm OK bit', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

FB_PTM_MagDriveDigital

(* This function block does basic controls FOR the Leybold connected to the MagDriveDigital Controller.
 Turns off pump in the event of errors/ warnings. Provides interlocking interface.*)
FUNCTION_BLOCK FB_PTM_MagDriveDigital EXTENDS FB_Pump
VAR_IN_OUT

END_VAR
VAR_INPUT

    i_xExtILKOk : BOOL; // Connect to external interlock logic, TRUE if not used.
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
    pv:
    '}
    iq_stPtm        :       ST_LeyboldPTM;
END_VAR
VAR
    xRunOk  :       BOOL;
    tofRemoteDelay  :       TOF;


    (*IO*)
    i_xAtSpd AT%I*: BOOL; // Normaml operation when true
    i_xFault AT%I*: BOOL; // Error NC
    i_xAccel AT%I*: BOOL;
    i_xDecel AT%I*: BOOL;
    i_xWarn AT%I*: BOOL;

    q_RunDO AT%Q*: BOOL; // Start/Stop
    q_xRemote AT%Q*: BOOL;
END_VAR
(* Basic MagDrive Digital Turbo Controls *)
(* A. Wallace, 2015-7-15 *)

tofRemoteDelay(IN:=iq_stPtm.q_RunDO, PT:=T#5S);

iq_stPtm.q_xRemote := tofRemoteDelay.Q;

(* Interlock Sum *)
iq_stPtm.xExtRunOk:= i_xExtILKOk;
xRunOk := iq_stPtm.xExtRunOk AND NOT iq_stPtm.i_xFault;

(* Basic pump supervisory section *)
IF xRunOk THEN
    iq_stPtm.q_RunDO := iq_stPTM.xRunSW;
ELSE
    iq_stPtm.xRunSW:=FALSE;
    iq_stPtm.q_RunDO:=FALSE;
END_IF

(*Pump STATE*)
IF iq_stPtm.i_xFault THEN
    iq_stPtm.eState := pumpFAULT;
ELSIF iq_stPTM.i_xDecel AND NOT iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpSTOPPING;
ELSIF NOT iq_stPtm.q_RunDO AND NOT iq_stPTM.i_xFault THEN
    iq_stPtm.eState := pumpSTOPPED;
ELSIF NOT iq_stPtm.i_xAtSpd  AND iq_stPtm.q_RunDO AND iq_stPTM.i_xAccel THEN
    iq_stPtm.eState := pumpSTARTING;
ELSIF iq_stPtm.i_xAtSpd AND iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpRUNNING;
ELSE
    iq_stPtm.eState := pumpFAULT;
END_IF


(*I/O soft mapping*)
ACT_IO();
// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*Inputs*)
iq_stPtm.i_xAtSpd := i_xAtSpd;
iq_stPtm.i_xFault := NOT i_xFault;
iq_stPtm.i_xAccel := i_xAccel;
iq_stPtm.i_xDecel := i_xDecel;
iq_stPtm.i_xWarn := i_xWarn;

(*Outputs*)
q_RunDO := iq_stPtm.q_RunDO;
q_xRemote := iq_stPtm.q_xRemote; // remote out
END_ACTION
ACTION ACT_Logger:
//STATE Logger
IF NOT i_xExtIlkOK AND ePrevState = pumpRUNNING THEN
            fbLogger(sMsg:='Lost external interlock while pump was running.', eSevr:=TcEventSeverity.Critical);
END_IF


IF ePrevState <> iq_stPTM.eState THEN
      CASE iq_stPTM.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stPTM.eState;
  END_IF

// ILK logger



// Log Action
tAction(CLK:=  iq_stPTM.q_RunDO);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF


// Log FAULT
tFault(CLK:= NOT iq_stPTM.i_xFault);
IF tFault.Q THEN fbLogger(sMsg:='Pump Lost Alarm OK bit', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

FB_PTM_Pfeiffer

(* This function block does basic controls FOR the Pfeiffer Turbo pump connected to the TM700 and TC400 Controllers.
 Turns off pump in the event of errors/ warnings. Provides interlocking interface.*)
FUNCTION_BLOCK FB_PTM_Pfeiffer EXTENDS FB_Pump
VAR_IN_OUT

END_VAR
VAR_INPUT
    i_xExtIlkOK : BOOL; // Connect to external interlock logic, TRUE if not used.
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stPTM : ST_PfeifferPTM;
END_VAR
VAR
    TOF_RESET: TON :=(PT:=T#1S);
    (*IO*)
    i_xAtSpd AT%I*: BOOL; // Normaml operation when true
    i_xFaultNC AT%I*: BOOL; // Error
    i_xWarn AT%I*: BOOL;
    i_xRemote AT%I*: BOOL;

    q_RunDO AT%Q*: BOOL; // Start/Stop for TC110 link this output to the Pumping station bit
    q_PumpingStation AT%Q*: BOOL; // Start/Stop interlock
    q_xRemote AT%Q*: BOOL;
    q_xReset AT%Q*: BOOL;
END_VAR
(* Basic pump supervisory section *)

q_xRemote := TRUE;
(* Interlock Sum *)
iq_stPtm.xExtRunOk:= i_xExtILKOk;
//For compatability with TC110
(*IF iq_stPTM.xExtRunOk THEN
    q_PumpingStation:= TRUE;
ELSE
    q_PumpingStation:= FALSE;
END_IF*)

IF iq_stPTM.xExtRunOk AND NOT (iq_stPTM.i_xFault) (*OR iq_stPTM.i_xTempFault)*) THEN
    iq_stPTM.q_RunDO:=iq_stPTM.xRunSW;
    q_PumpingStation:=iq_stPTM.xRunSW;
ELSE
    iq_stPTM.xRunSW:=FALSE;
    iq_stPTM.q_RunDO:=FALSE;
    q_PumpingStation:=FALSE;
END_IF


(*Pump STATE*)
IF iq_stPtm.i_xFault THEN
    iq_stPtm.eState := pumpFAULT;
ELSIF NOT iq_stPtm.q_RunDO AND NOT iq_stPTM.i_xFault THEN
    iq_stPtm.eState := pumpSTOPPED;
ELSIF NOT iq_stPtm.i_xAtSpd  AND iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpSTARTING;
ELSIF iq_stPtm.i_xAtSpd AND iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpRUNNING;
ELSE
    iq_stPtm.eState := pumpFAULT;
END_IF

(*Error Reset*)
(* to reset the Reset Bit *)
 IF (iq_stPTM.xResetSW ) THEN  iq_stPTM.q_xReset := TRUE;
 END_IF
 TOF_RESET (IN:= iq_stPTM.q_xReset, Q => );
 IF TOF_RESET.Q THEN
    iq_stPTM.q_xReset:= FALSE;
    iq_stPTM.xResetSW := FALSE;
 END_IF

(*Soft IO Mapping*)
ACT_IO();
// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*outputs*)
q_RunDO := iq_stPTM.q_RunDO;
q_xReset := iq_stPTM.q_xReset;
//q_xRemote := iq_stPTM.
//q_PumpingStation AT%Q*: BOOL; // Start/Stop

(*inputs*)
iq_stPTM.i_xAtSpd:= i_xAtSpd;
iq_stPTM.i_xFault:= NOT i_xFaultNC;
iq_stPTM.i_xWarn:=          i_xWarn ;
END_ACTION
ACTION ACT_Logger:
//STATE Logger
IF NOT i_xExtIlkOK AND ePrevState = pumpRUNNING THEN
            fbLogger(sMsg:='Lost external interlock while pump was running.', eSevr:=TcEventSeverity.Critical);
END_IF


IF ePrevState <> iq_stPTM.eState THEN
      CASE iq_stPTM.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stPTM.eState;
  END_IF

// ILK logger



// Log Action
tAction(CLK:=  iq_stPTM.q_RunDO);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF


// Log FAULT
tFault(CLK:= NOT iq_stPTM.i_xFault);
IF tFault.Q THEN fbLogger(sMsg:='Pump Lost Alarm OK bit', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

FB_PTM_Test

FUNCTION_BLOCK FB_PTM_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    fb_TwisTorr: FB_PTM_TwisTorr;
    fb_PTM_Ebara_010M:FB_PTM_Ebara_010M;

    cycle:INT :=0;
    (*IO*)
    q_iSpeedSet AT %I* :INT;
    i_diCurSpd AT %Q* :INT;

    i_iRawSpeed AT %Q* : INT; // Link to Analog input
    i_iTempMon AT %Q* : INT; // Link to Analog input -- input Voltage according to the pump temprature 0->5V 0->100C
    i_iCurrentMon AT %Q* : INT; //Link to Analog -- input Voltage to the output current of motor 0->5V 0->10A

END_VAR
M_INIT();
M_Interlock();
M_PTM_EBARA();
cycle:=cycle+1;

END_FUNCTION_BLOCK

FB_PTM_TurboDrive

(* This Function block provides basic turbo control for Leybold Turbo Drive 300, Turbo Drive 400 *)
(* TD20 Classic Via Remote X1 Connector 9-way PLC interface*)
(* When serial interface is implemented, call Method M_Serial_IO after fb instantiation, in order to add the serial status *)
FUNCTION_BLOCK FB_PTM_TurboDrive EXTENDS FB_Pump
VAR_IN_OUT
END_VAR
VAR_INPUT
    i_xExtILKOk : BOOL; // Connect to external interlock logic, TRUE if not used.
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stPtm        :       ST_LeyboldPTM;
END_VAR
VAR

    xRunOk  :       BOOL;
    tofRemoteDelay  :       TOF;


    (*IO*)
    i_xAtSpd AT%I*: BOOL; // Normaml operation when true
    i_xFaultNC AT%I*: BOOL; // Error Active when no Error is present
    q_RunDO AT%Q*: BOOL; // Start/Stop

END_VAR
(* Basic Turbo Control *)
(* This Function block provides basic turbo control for Leybold Turbo Drive 300, Turbo Drive 400 *)
(* TD20 Classic Via Remote X1 Connector 9-way PLC interface*)
(* M. Ghaly Jan. 2019 *)

(* Interlock Sum *)
iq_stPtm.xExtRunOk:= i_xExtILKOk;
xRunOk := iq_stPtm.xExtRunOk AND iq_stPtm.i_xNCError;

(* Basic pump supervisory section *)
IF xRunOk THEN
    iq_stPtm.q_RunDO := iq_stPTM.xRunSW;
ELSE
    iq_stPtm.xRunSW:=FALSE;
    iq_stPtm.q_RunDO:=FALSE;
END_IF

(*Pump States*)
IF (iq_stPtm.i_xFault) THEN
            iq_stPtm.eState := pumpFAULT;
            iq_stPtm.xRunSW:=FALSE;
            iq_stPtm.q_RunDO:=FALSE;
ELSIF (iq_stPtm.q_RunDO) AND (iq_stPtm.i_xAtSpd) THEN
            iq_stPtm.eState := pumpRUNNING;
ELSIF (iq_stPtm.q_RunDO) AND NOT (iq_stPtm.i_xAtSpd) THEN
            iq_stPtm.eState := pumpSTARTING;
ELSIF NOT (iq_stPtm.q_RunDO) THEN
            iq_stPtm.eState := pumpSTOPPED;
ELSE
    iq_stPtm.eState := pumpFAULT;
END_IF;


(*I/O soft mapping*)
ACT_IO();
// Log States and triggers
This^.ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*Inputs*)
iq_stPtm.i_xAtSpd := i_xAtSpd;
iq_stPtm.i_xNCError := i_xFaultNC;
iq_stPtm.i_xFault := NOT i_xFaultNC;



(*Outputs*)
q_RunDO := iq_stPtm.q_RunDO;
END_ACTION
ACTION ACT_Logger:
//STATE Logger
// ILK logger
IF NOT i_xExtIlkOK AND ePrevState = pumpRUNNING THEN
            fbLogger(sMsg:='Lost external interlock while pump was running.', eSevr:=TcEventSeverity.Critical);
END_IF


IF ePrevState <> iq_stPTM.eState THEN
      CASE iq_stPTM.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stPTM.eState;
  END_IF

// Log Action
tAction(CLK:=  iq_stPTM.q_RunDO);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF


// Log FAULT
tFault(CLK:= NOT iq_stPTM.i_xFault);
IF tFault.Q THEN fbLogger(sMsg:='Pump Lost Alarm OK bit', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

FB_PTM_TwisTorr

(* This function block does basic controls FOR the Agilent TwisTorr 304 FS with on board Controller.
 Turns off pump in the event of errors/ warnings. Provides interlocking interface.*)
FUNCTION_BLOCK FB_PTM_TwisTorr EXTENDS FB_Pump
VAR_INPUT
    i_xExtILKOk : BOOL; // Connect to external interlock logic, TRUE if not used.

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := ' pv: '}
    iq_stPtm        :       ST_PTM;
END_VAR
VAR
    xRunOk  :       BOOL;
    (*IO*)
    i_xAtSpd AT%I*: BOOL; // Normaml operation when true
    i_xFault AT%I*: BOOL; // Error // this open collector output signal is ON when a system fault condition is detected

    q_RunDO AT%Q*: BOOL; // Start/Stop

END_VAR
(* Interlock Sum *)
iq_stPtm.xExtRunOk:= i_xExtILKOk;
xRunOk := iq_stPtm.xExtRunOk AND NOT iq_stPtm.i_xFault;

(* Basic pump supervisory section *)
IF xRunOk THEN
    iq_stPtm.q_RunDO := iq_stPTM.xRunSW;
ELSE
    iq_stPtm.xRunSW:=FALSE;
    iq_stPtm.q_RunDO:=FALSE;
END_IF

(*Pump STATE*)
IF iq_stPtm.i_xFault THEN
    iq_stPtm.eState := pumpFAULT;
ELSIF NOT iq_stPtm.q_RunDO AND NOT iq_stPTM.i_xFault THEN
    iq_stPtm.eState := pumpSTOPPED;
ELSIF NOT iq_stPtm.i_xAtSpd  AND iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpSTARTING;
ELSIF iq_stPtm.i_xAtSpd AND iq_stPtm.q_RunDO THEN
    iq_stPtm.eState := pumpRUNNING;
ELSE
    iq_stPtm.eState := pumpFAULT;
END_IF

(*I/O soft mapping*)
ACT_IO();
// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*Inputs*)
iq_stPtm.i_xAtSpd := i_xAtSpd;
iq_stPtm.i_xFault := i_xFault;


(*Outputs*)
q_RunDO := iq_stPtm.q_RunDO;
END_ACTION
ACTION ACT_Logger:
//STATE Logger
// ILK logger
IF NOT i_xExtIlkOK AND ePrevState = pumpRUNNING THEN
            fbLogger(sMsg:='Lost external interlock while pump was running.', eSevr:=TcEventSeverity.Critical);
END_IF


IF ePrevState <> iq_stPTM.eState THEN
      CASE iq_stPTM.eState OF
            pumpFAULT:
                    fbLogger(sMsg:='Pump Fault.', eSevr:=TcEventSeverity.Critical);
            pumpSTOPPED:
                    fbLogger(sMsg:='Pump stopped.', eSevr:=TcEventSeverity.Critical);
            pumpSTARTING:
                    fbLogger(sMsg:='Pump starting.', eSevr:=TcEventSeverity.Info);
            pumpRUNNING:
                    fbLogger(sMsg:='Pump running.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stPTM.eState;
  END_IF

// Log Action
tAction(CLK:=  iq_stPTM.q_RunDO);
IF tAction.Q THEN fbLogger(sMsg:='Pump commanded to start', eSevr:=TcEventSeverity.Info); END_IF


// Log FAULT
tFault(CLK:= NOT iq_stPTM.i_xFault);
IF tFault.Q THEN fbLogger(sMsg:='Pump Lost Alarm OK bit', eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

FB_Pump

FUNCTION_BLOCK FB_Pump
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR

 // For logging
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.VACUUM);
    ePrevState : E_PumpState;
    tErrorPresent : R_TRIG;
    tAction : R_TRIG; // Primary action of this device (OPN_DO, PUMP_RUN, etc.)
    tFault : F_TRIG;
    tILK: TON;
END_VAR


END_FUNCTION_BLOCK
ACTION ACT_Logger:

END_ACTION

FB_ScrollPump

(* This Function block provides basic  control for Scroll Pumps *)
(* Turn on scroll pump if the Turbo Vent Valve is closed. *)
(* Cannot turn off scroll pump if turb is running. delay scroll off 150s.*)

FUNCTION_BLOCK FB_ScrollPump
VAR_IN_OUT

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stPFO        :       ST_RoughPump;
END_VAR
VAR_INPUT
    TurboIsON : BOOL;
    xExtIlk : BOOL;
END_VAR
VAR
    Tmr_TOF : TOF;
    tTimerDuration : Time:= T#150S;
    //Previous_RunSW : BOOL;
    // Digital Output to be linked
    q_xRunDo AT%Q*  :   BOOL;
END_VAR
//Turn on scroll pump if the Turbo Vent Valve is closed. So you are not just
// pumping air and accomplishing nothing

// cannot turn off scroll pump if turb is running. delay scroll off 150s.
iq_stPFO.xIlkOK := xExtIlk;// and NOT Tmr_TOF.Q;

Tmr_TOF(IN:=TurboIsON, PT := tTimerDuration);

IF iq_stPFO.xIlkOK THEN
    iq_stPFO.q_xRunDo := (Tmr_TOF.Q OR iq_stPFO.pv_xRunSW);
ELSE
    iq_stPFO.q_xRunDo := FALSE;
END_IF

iq_stPFO.pv_xRunSW := iq_stPFO.q_xRunDo;

(*State evaluation*)
IF NOT iq_stPFO.q_xRunDo THEN
    iq_stPFO.eState := pumpSTOPPED;
ELSE
    iq_stPFO.eState := pumpRUNNING;
END_IF

(*soft io Mapping*)
IO();

END_FUNCTION_BLOCK
ACTION IO:
q_xRunDo:= iq_stPFO.q_xRunDo;
END_ACTION

FB_TGCC_ADS

(* This function block is created for interface devices between different PLC*)
(* Not all the Variables in the original structure is required, just few signals *)
(* The variable values are read via ADS using the symbol name*)
FUNCTION_BLOCK FB_TGCC_ADS Extends FB_ADS
VAR_INPUT
    sNetId : String; //NetID of the Destination PLC controller
    nPort : uint; // port number
    sVarName : string;// the variable name of the declared gauge function block.
    iWatchdog:UDINT;//The watchdog variable name written to by the remote plc
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv:'}
    IG : ST_VG;
    bError: BOOL;
END_VAR
VAR
    fb_CheckWatchdog: FB_CheckWatchdog;
    fb_Read_VG: FB_ReadAdsSymByName;
    ftReset: F_TRIG;
    xFirstPass: BOOL:= true;
END_VAR
ftReset(CLK:= fb_Read_VG.bBusy OR xFirstPass);
xFirstPass := false;
(*calling ADS read function*)

//IG.xPRESS_OK := false;

fb_Read_VG(
    bRead:=ftReset.Q ,
    sNetId:= sNetId,
    nPort:= nPort,
    sVarName:= CONCAT(sVarName,'.IG'),
    nDestAddr:= ADR(IG),
    nLen:= SIZEOF(IG),
    tTimeout:= ,
    eComMode:=eAdsComModeFastCom ,
    bBusy=> ,
    bError=> ,
    nErrorId=> );


(*Error*)
fb_CheckWatchdog(
    bEnable:= TRUE,
    tWatchdogTime:= T#900ms,
    nCnt:= iWatchdog ,
    bWatchdog=> ,
    nLastCnt=> );

bError:= fb_Read_VG.bError OR fb_CheckWatchdog.bWatchdog;
tErrorPresent(CLK:=bError);

IF (fb_Read_VG.bError OR fb_CheckWatchdog.bWatchdog) THEN
IG.xPRESS_OK := FALSE;
END_IF;

(*Logger*)
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
IF tErrorPresent.Q THEN
 IF(fb_Read_VG.bError) THEN fbLogger(sMsg:='ADS Read Error', eSevr:=TcEventSeverity.Critical); END_IF;
 IF(fb_CheckWatchdog.bWatchdog) THEN fbLogger(sMsg:='ADS Watchdog Error', eSevr:=TcEventSeverity.Critical); END_IF;
END_IF
END_ACTION

FB_TPIP_ADS

(* This function block is created for interface devices between different PLC*)
(* Not all the Variables in the original structure is required, just few signals *)
(* The variable values are read via ADS using the symbol name*)
FUNCTION_BLOCK FB_TPIP_ADS Extends FB_ADS
VAR_INPUT
    sNetId : String; //NetID of the Destination PLC controller
    nPort : uint; // port number
    sVarName : string;// the variable name of the declared pip/pin function block.
    iWatchdog:UDINT;//The watchdog variable name written to by the remote plc
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv:'}
    IG : ST_VG;
    bError: BOOL;
END_VAR
VAR
    fb_CheckWatchdog: FB_CheckWatchdog;
    fb_Read_VG: FB_ReadAdsSymByName;
    ftReset: F_TRIG;
    xFirstPass: BOOL:= true;

END_VAR
ftReset(CLK:= fb_Read_VG.bBusy OR xFirstPass);
xFirstPass := false;
(*calling ADS read function*)

fb_Read_VG(
    bRead:=ftReset.Q ,
    sNetId:= sNetId,
    nPort:= nPort,
    sVarName:= CONCAT(sVarName,'.q_IG'),
    nDestAddr:= ADR(IG),
    nLen:= SIZEOF(IG),
    tTimeout:= ,
    eComMode:=eAdsComModeFastCom ,
    bBusy=> ,
    bError=> ,
    nErrorId=> );


(*Error*)
fb_CheckWatchdog(
    bEnable:= TRUE,
    tWatchdogTime:= T#900ms,
    nCnt:= iWatchdog ,
    bWatchdog=> ,
    nLastCnt=> );

bError:= fb_Read_VG.bError OR fb_CheckWatchdog.bWatchdog;
tErrorPresent(CLK:=bError);

IF (fb_Read_VG.bError OR fb_CheckWatchdog.bWatchdog) THEN
IG.xPRESS_OK := FALSE;
END_IF;

(*Logger*)
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
IF tErrorPresent.Q THEN
 IF(fb_Read_VG.bError) THEN fbLogger(sMsg:='ADS Read Error', eSevr:=TcEventSeverity.Critical); END_IF;
 IF(fb_CheckWatchdog.bWatchdog) THEN fbLogger(sMsg:='ADS Watchdog Error', eSevr:=TcEventSeverity.Critical); END_IF;
END_IF
END_ACTION

FB_TurboExtLogic

(*deprecated*)
FUNCTION_BLOCK FB_TurboExtLogic
VAR_IN_OUT
    Turbo           :       ST_AgilentPTM;
END_VAR
VAR_INPUT
    BackingGauge:   ST_VG;
    InletGauge      :       ST_VG;
    VentValve       :       ST_VCC_NO;
    ScrollPump      :       ST_RoughPump;
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR


END_FUNCTION_BLOCK

FB_TurbVentvalve_NO

(* This function block impelemets Interlock for Agilent Turbo Vent Valve Normally Open*)
FUNCTION_BLOCK FB_TurbVentvalve_NO
VAR_OUTPUT
    {attribute 'pytmc' :=' pv:'}
    q_stValve : ST_VCC_NO;
END_VAR
VAR_INPUT
    i_stPTM : ST_AgilentPTM; //Agilent Turbo Pump
END_VAR
VAR
    tofTmr          :       TOF;

    (*Output*)
    q_xCLS_DO AT%Q* : BOOL;
END_VAR

(*If the turbo is on, you cannot open the valve. In order to open the valve, you have to allow the turbopump
 to have already been turned off for 2 minutes and you have to press the open button on the vent valve

If the code looks confusing please use a truth table to verify*)


END_FUNCTION_BLOCK
ACTION ACT_IO:
(*outputs*)
q_xCLS_DO:= q_stValve.xCLS_DO;
END_ACTION

FB_TVGC_ADS

(* This function block is created for interface devices between different PLC*)
(* Not all the fields in the original structure is required, just few signals *)
(*Use with FB_ADS_WATCHDOG on remotePLC*)
FUNCTION_BLOCK FB_TVGC_ADS extends FB_ADS
VAR_INPUT
    sNetId : String; //NetID of the Destination PLC controller
    nPort : UINT; // port number
    sVarName : string;// the variable name of the (device) declared function block.
    iWatchdog:UDINT;//The watchdog variable name written to by the remote plc
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv: '}
    VGC:ST_VGC;
    bError:BOOL;
END_VAR
VAR
    fb_CheckWatchdog: FB_CheckWatchdog;
    fb_Read_VGC: FB_ReadAdsSymByName;
    ftReset: F_TRIG;
    xFirstPass: bool:= true;
END_VAR
ftReset(CLK:= fb_Read_VGC.bBusy OR xFirstPass);
xFirstPass:=false;
(*calling ADS read function*)
fb_Read_VGC(
    bRead:= ftReset.Q,
    sNetId:= sNetId,
    nPort:= nPort,
    sVarName:= CONCAT(sVarName,'.iq_stValve'),
    nDestAddr:= ADR(VGC),
    nLen:= SIZEOF(VGC),
    tTimeout:= ,
    eComMode:= eAdsComModeFastCom,
    bBusy=> ,
    bError=> ,
    nErrorId=> );


(*Error*)
fb_CheckWatchdog(
    bEnable:= TRUE,
    tWatchdogTime:= T#900ms,
    nCnt:= iWatchdog ,
    bWatchdog=> ,
    nLastCnt=> );
bError:= fb_Read_VGC.bError OR fb_CheckWatchdog.bWatchdog;

ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
IF tErrorPresent.Q THEN
 IF(fb_Read_VGC.bError) THEN fbLogger(sMsg:='ADS Read Error', eSevr:=TcEventSeverity.Critical); END_IF;
 IF(fb_CheckWatchdog.bWatchdog) THEN fbLogger(sMsg:='ADS Watchdog Error', eSevr:=TcEventSeverity.Critical); END_IF;
END_IF
END_ACTION

FB_Valve

FUNCTION_BLOCK FB_Valve
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    // For logging
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.VACUUM);
    ePrevState : E_ValvePositionState;
    tErrorPresent : R_TRIG;
    tAction : R_TRIG; // Primary action of this device (OPN_DO, etc.)
    tOverrideActivated : R_TRIG;
END_VAR


END_FUNCTION_BLOCK
ACTION ACT_Logger:

END_ACTION

FB_Valve_Interface

(* This function block is created for interface devices between different PLC*)
(* Not all the fields in the original structure is required, just few signals *)
(* They have to be linked to the custom created variables on the EL6692/5 primary side*)

FUNCTION_BLOCK FB_Valve_Interface
VAR_INPUT
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := 'pv: '}
    VGC:ST_VGC;
END_VAR
VAR
    i_xOpnLS AT%I*  : BOOL;
    i_xClsLS AT%I*  : BOOL;
END_VAR
(* This function block is created for interface devices between different PLC*)
(* Not all the fields in the original structure is required, just few signals *)
(* They have to be linked to the custom created variables on the EL6692/5 primary side*)


VGC.i_xClsLS := i_xClsLS;
VGC.i_xOpnLS := i_xOpnLS;

END_FUNCTION_BLOCK

FB_VCC

(*Deprecated*)
(* THE FB_VCC FUNCTION BLOCK IS DEPRECATED. USE FB_VVC INSTEAD. *)
(* This Function Block Implements Basic Functionality for Vent Valves VVC *)
(* Note Interlock Logic is External *)
FUNCTION_BLOCK FB_VCC
VAR_IN_OUT

END_VAR
VAR_INPUT
    i_xExtILK_OK:BOOL; (*Other External Interlock, Set to True when no external interlock is required*)
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only*)
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stValve : ST_VVC;
END_VAR
VAR

    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;

    (*IO*)
    q_xOPN_DO       AT%Q*: BOOL;

END_VAR
iq_stValve.xOPN_OK := i_xExtILK_OK;

IF NOT iq_stValve.xOPN_OK THEN
iq_stValve.pv_xOPN_SW := FALSE;
END_IF

(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xOPN_OK, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q THEN iq_stValve.xOvrdOpn :=FALSE; END_IF

//Override timer
tonOvrd(IN:=iq_stValve.xOvrdOpn, PT:=T#10S);

(* Here's where the valve opens *)
iq_stValve.q_xOPN_DO := (iq_stValve.pv_xOPN_SW AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);

(*IO Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
q_xOPN_DO:= iq_stValve.q_xOPN_DO;
END_ACTION

FB_VCC_NO

(*Deprecated*)
(* This Function Block Implements Basic Functionality for NO Vent Valves VVC *)
(* Note Interlock Logic is External *)
FUNCTION_BLOCK FB_VCC_NO
VAR_IN_OUT
    {attribute 'pytmc' := 'pv:'}
    iq_stValve : ST_VCC_NO;

END_VAR
VAR_INPUT
    i_xExtILK_OK:BOOL; (*Other External Interlock, Set to True when no external interlock is required*)
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)

END_VAR
VAR_OUTPUT
END_VAR
VAR

    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;
    (*IO*)
    xCLS_DO AT%Q*: BOOL;

END_VAR
IF NOT iq_stValve.xCLS_OK THEN
iq_stValve.pv_xCLS_SW := FALSE;
END_IF

(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xCLS_OK, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q THEN iq_stValve.pv_xOvrdCls :=FALSE; END_IF

//Override timer
tonOvrd(IN:=iq_stValve.pv_xOvrdCls, PT:=T#10S);

(* Here's where the valve is closed *)
iq_stValve.xCLS_DO := (iq_stValve.pv_xCLS_SW AND iq_stValve.xCLS_OK) OR (tonOvrd.Q AND i_xOverrideMode);

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
xCLS_DO:= iq_stValve.xCLS_DO;
END_ACTION

FB_VCN

(* This function implements the Basic functions for the Pfeiffer EVR 116 needle valve*)
FUNCTION_BLOCK FB_VCN
VAR_INPUT
    i_xExtIlkOK     :       BOOL; //External Interlock, SET to TRUE if not used
    i_ReqPos        :       REAL; //Requested position

END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stVCN        :       ST_VCN; //Needle valve structure
END_VAR
VAR_IN_OUT

END_VAR

VAR CONSTANT
    rOpenVoltage    :       REAL := 9.1; // From the EVR 116 manual, A Voltage of 9V it is completely Open
    rCloseVoltage   :       REAL := 0.4;// a voltage <0.5 V the valve is closed
END_VAR
VAR
    // Requested voltage
    {attribute 'pytmc' := '
    pv: POS_AO;
    io: i ;
    '}
    rReqVoltage: REAL := 0;
    //iResolution: INT :=16;
    //
    (*IO*)
    q_iRawPosition AT%Q* :INT;

END_VAR
(* Needle valve control FB
A. Wallace 2016-7-21

This FB should be used as a low level control block.

It provides:

Valve position ceiling
Interlock
Scaling

It is not intended for:
Closed-loop control

It could be used for:
Valve position/flow linearization

Note: Raw position calc is based on 0.5 to 9V span, 32767 bits
*)

// Interlocking
iq_stVCN.xIlkOK := i_xExtIlkOK;
(*Checking which Control mode is selected*)
IF iq_stVCN.xIlkOK THEN
    IF iq_stVCN.eValveControl = OpenValve THEN
            iq_stVCN.rReqPosition := iq_stVCN.rUpperLimit;(*Percentage*)// iq_stVCN.rUpperLimit;
    ELSIF iq_stVCN.eValveControl = CloseValve THEN
            iq_stVCN.rReqPosition := 0; (*Percentage*)
    ELSIF (iq_stVCN.eValveControl = ManualControl) (*AND (iq_stVCN.xOPN_SW)*) THEN
            iq_stVCN.rReqPosition := LIMIT(0, iq_stVCN.rReqPosition, iq_stVCN.rUpperLimit);
    ELSIF iq_stVCN.eValveControl = PressureControl THEN
            iq_stVCN.rReqPosition := LIMIT(0, i_ReqPos, iq_stVCN.rUpperLimit);
    END_IF
ELSE
    iq_stVCN.rReqPosition := 0;
    iq_stVCN.eValveControl := CloseValve;
END_IF

// Requested Voltage calculation
rReqVoltage := iq_stVCN.rReqPosition * (rOpenVoltage-rCloseVoltage)/100 + rCloseVoltage;
rReqVoltage := LIMIT(rCloseVoltage, rReqVoltage, rOpenVoltage); //The requested voltage should remain within this range
//Raw position calc
iq_stvcn.q_iRawPosition := REAL_TO_INT( 32767/10 * rReqVoltage);

(*SOft IO Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*outputs*)
q_iRawPosition := iq_stVCN.q_iRawPosition;
(*inputs*)
iq_stVCN.i_iPosition := iq_stvcn.q_iRawPosition;
END_ACTION

FB_VFS

(* This function block implements basic functionality for Fast Shutter/Valves*)
(* Create a separate virtual PLC for the fast shutter logic, and tie/map this task to the FAST I/O portion of the ethercat bus,
 so we can scan these I/Os faster than the rest of the vacuum I/Os.
The Fast shutter was tested with PLC task Cycle Base Time 50us with cycle time 0.050ms.
*)
FUNCTION_BLOCK FB_VFS EXTENDS FB_Valve
VAR_INPUT
    i_xPMPS_OK:     BOOL    ; (*External MPS interlock, Set to TRUE when no PMPS interlock is required*)
    i_xExt_OK: BOOL; (*Other External Interlock, Set to True when no external interlock is required*)
    i_sDevName : T_MaxString :=  'VGC'; // Device name for diagnostic
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR
    xOPN_OK :BOOL;
    xERR_ExtFault :BOOL;
    // PMPS
    fbFF    :    FB_FastFault :=(
        i_DevName := 'VFS',
        i_Desc := 'Fault occurs when fast valve is not in open state',
        i_TypeCode := 16#1010);

    rDiffPressAllowed       :       REAL := 22.5; // Torr, Default value comes from Vat Valve Manual
    rDiffPress : REAL;
    set : BOOL;
    reset: BOOL;

    xFirstPass      :       BOOL := TRUE;
    //fbFSInit              :       R_TRIG;
    rtOPN_SW :      R_TRIG;

    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tonOvrd :       TON;

    tDelOK  :       TIME := T#60S;
    tOvrd   :       TIME := T#10s;

    xClose: BOOL;
    xOpen: BOOL;
    CloseTimer: TON;
    TimDur: TIME := T#20MS;
    OpenTimer: TON;
    OpenTimDur: TIME := T#2S;

    (* Timeouts*)
    tCLSTimeOutDuration: TIME:= T#0.5S;
    tOPNTimeOutDuration: TIME:= T#10S;
    tOPNtimeout: TON;
    tCLStimeout:TON;


    (*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xClose_A      AT%Q*: BOOL;
    q_xClose_B      AT%Q*: BOOL;
    q_xClose_C      AT%Q*: BOOL;
    q_xOPN_DO       AT%Q*: BOOL;
    i_xTrigger      AT%I*: BOOL;

    // Interface
    i_xPress_OK AT%I*:BOOL; (*To be linked to the Fast sensor structure signal IG.xPRESS_OK, to make sure that the device is connected*)
    i_xOPN_SW AT%I*:BOOL;
    i_xCLS_SW AT%I*:BOOL; (*external open signal e.g epics*)
    i_xVAC_FAULT_Reset      AT%I*: BOOL;(*Valve Vacuum OK, is set to False when there is Vacuum Fault*)
    i_xOverrideMode AT%I*: BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)
    i_xOverrideOpen AT%I*: BOOL;
    q_xTrigger      AT%Q*: BOOL; (*Interface*)
    q_xVFS_Open AT%Q*: BOOL; (*Interface*)
    q_xVFS_Closed AT%Q*: BOOL; (*Interface*)
    q_xVAC_FAULT_OK AT%Q*: BOOL; (*Valve Vacuum OK, is set to False when there is Vacuum Fault*)
    q_xMPS_OK        AT%Q*: BOOL; (*MPS Fault OK, is set when the Valve is Open and there is no trigger*)
    q_eVFS_State AT%Q*: E_VGC; (*Interface*)

END_VAR
(* Fast Shutter/Valve Function Block*)
(* M.Ghaly Feb 2019*)

(* Fast Shutters do not function as complete isolation valves, but are intended to rapidly limit conductance
between vacuum regions.  They should be installed in conjunction with isolation gate valves.  The conductance
of the fast shutter in the closed state may be sufficiently low as to create an isolated vacuum region.
Thus, at least a pirani gauge, that can measure a pressure down to 1x10-4 Torr, needs to be installed between
the shutter and the isolation gate valve.
In the case where a gauge is not installed, the neigboring gate Valves should not be opened unless the shutter
is Open.
*)
(* On first PLC pass, put valve into vented state, which implies a closed valve *)
IF xFirstPass THEN
    fbFF.i_DevName := i_sDevName;
    xFirstPass := FALSE;
END_IF

(* Input Trigger to close the Fast Shutter*)
(* Neighboring Valves Should also be triggered to Close*)
IF (i_xTrigger) OR NOT (i_xPress_OK) OR NOT (i_xExt_OK)  OR i_xCLS_SW  THEN
    xClose:= TRUE;
    xOpen:= FALSE;
    q_xVAC_FAULT_OK:= FALSE;
    IF (NOT i_xCLS_SW) THEN xERR_ExtFault := TRUE; END_IF;
    i_xCLS_SW := TRUE;
    q_xOPN_DO := FALSE;
    //i_xOPN_SW :=FALSE;
    xOpen:=FALSE;
ELSIF ((rtOPN_SW.Q) AND xOPN_OK AND NOT i_xOpnLS) OR (tonOvrd.Q AND i_xOverrideMode) THEN
    q_xOPN_DO := TRUE;
END_IF
IF (xClose) AND (NOT i_xClsLS) THEN
    q_xClose_A := TRUE;
    q_xClose_B := TRUE;
    q_xClose_C := TRUE;
END_IF
IF(CloseTimer.Q) OR (i_xClsLS) THEN
    q_xClose_A := FALSE;
    q_xClose_B := FALSE;
    q_xClose_C := FALSE;
    xClose := FALSE;
    //i_xOPN_SW := FALSE;
END_IF

IF(OpenTimer.Q) AND (i_xOpnLS) THEN
    q_xOPN_DO :=  FALSE;
    i_xOPN_SW := FALSE;
    xOpen:=FALSE;
END_IF

(* Neighboring Valves Interlock bit is reset When the Vacuum event error is reset and the Shutter is opened *)
IF (NOT i_xTrigger) AND (NOT xERR_ExtFault) AND i_xOpnLS THEN
    q_xVAC_FAULT_OK := TRUE;
END_IF

(*OPN OK evaluation bit *)
xOPN_OK := (NOT xERR_ExtFault) AND NOT i_xTrigger;

(* If Epics Command to close the Valve, check if PMPS, otherwise Keep command set to open valve *)
// is there really a PMPS requirment.



(* When the valve  is open MPS is OK*)
q_xMPS_OK := i_xOpnLS AND NOT i_xTrigger AND i_xPress_OK AND NOT(i_xCLS_SW);


(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=xOPN_OK, PT:=tDelOK);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND i_xOverrideOpen THEN
    i_xOverrideOpen :=FALSE;
    //if (iq_stValve.eState = OPEN) AND (i_xOverrideMode) THEN iq_stValve.pv_xOPN_SW := TRUE; END_IF //for seamless transition
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF

//Override timer
tonOvrd(IN:=i_xOverrideOpen, PT:=tOvrd);


//Assign valve state
IF (i_xTrigger) THEN q_eVFS_State := E_VGC.Triggered;
ELSIF xERR_ExtFault THEN q_eVFS_State :=  E_VGC.Vac_Fault;
ELSIF NOT i_xClsLS AND tCLStimeout.Q THEN
    q_eVFS_State :=  E_VGC.Cls_Timeout;
ELSIF NOT i_xOpnLS AND tOPNtimeout.Q THEN
    q_eVFS_State :=  E_VGC.Opn_Timeout;
ELSE q_eVFS_State :=  E_VGC.At_Vac;
END_IF

(*Timers*)
CloseTimer ( IN:=xClose, PT:= TimDur, Q=>);
OpenTimer ( IN:=i_xOpnLS, PT:= OpenTimDur, Q=>);
tOPNtimeout(IN:= q_xOPN_DO, PT := tOPNTimeOutDuration);
tCLStimeout(IN:= q_xClose_A, PT := tCLSTimeOutDuration);
rtOPN_SW(CLK:= i_xOPN_SW);

(* Soft IO Mapping*)
IO();

(*Alarm reset *)
ACT_ResetAlarms();

(*FAST FAULT*)
fbFF(i_xOK := q_xMPS_OK,
    i_xReset := i_xVAC_FAULT_Reset,
    i_xAutoReset :=TRUE,
    io_fbFFHWO := io_fbFFHWO);

END_FUNCTION_BLOCK
ACTION ACT_ResetAlarms:
//iq_stValve.xERR_DifPres R= i_xResetFault;

//iq_stValve.bErrorPresent R= i_xResetFault;

IF NOT i_xTrigger AND i_xPress_OK THEN
    xERR_ExtFault R= i_xVAC_FAULT_Reset;
    i_xVAC_FAULT_Reset:=FALSE;
END_IF
END_ACTION
ACTION IO:
q_xTrigger := i_xTrigger;
q_xVFS_Closed:= i_xClsLS;
q_xVFS_Open:= i_xOpnLS;
END_ACTION

FB_VFS_Interface

(*Used as soft IO mapping to create a psuedo valve to communicate over two task on the same PLC.*)
(*for FAST shutter control*)
FUNCTION_BLOCK FB_VFS_Interface EXTENDS FB_Valve
VAR_INPUT
    IG : ST_VG; // The MKS422 Cold Cathode Data Structure
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv: ;
    '}
    iq_stValve : ST_VFS; (* All valve data and states will be in this struct*)
    i_xVAC_FAULT_OK AT%I*: BOOL; (*Valve Vacuum OK, is set to False when there is Vacuum Fault*)
END_VAR
VAR

    tonOvrd :       TON;
    tOvrd   :       TIME := T#10s;
    (*outputs*)
    q_xPRESS_OK AT%Q*: BOOL;
    q_xOPN_SW AT%Q*:BOOL;
    q_xCLS_SW AT%Q*:BOOL; (*external open signal e.g epics*)
    q_xVAC_FAULT_Reset AT%Q*: BOOL; (*Valve Vacuum OK, is set to False when there is Vacuum Fault*)
    q_xOverrideMode AT%Q*: BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)
    q_xOverrideOpen AT%Q*: BOOL;

    (*inputs*)
    i_xTrigger AT %I* : BOOL;
    i_xVFS_Open AT %I* : BOOL;
    i_xVFS_Closed AT %I* : BOOL;
    {attribute 'pytmc' := '
    pv: MPS_FAULT_OK
    field: ZNAM MPS FAULT ;
    field: ONAM MPS OK ;
    io: i ;
    '}
    i_xMPS_OK AT%I*: BOOL;(*MPS Fault OK, is set when the Valve is Open and there is no trigger*)
    i_eVFS_State AT%I*: E_VGC; (*Interface*)
END_VAR
(*soft IO Mapping*)

q_xPRESS_OK := IG.xPRESS_OK;

(* Soft IO Mapping*)
IO();
///Check valve postion
IF iq_stValve.i_xClsLS AND  iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=INVALID;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=OPEN;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=CLOSED;
ELSIF NOT iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=MOVING;
ELSE
    iq_stValve.eState:=INVALID;
END_IF
//Override timer
tonOvrd(IN:=q_xOverrideOpen, PT:=tOvrd);
(* Soft IO Mapping*)
IO();
(*Logger*)
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_Logger:
// ILK logger

IF i_xTrigger AND ePrevState = OPEN THEN
            fbLogger(sMsg:='Fast valve triggered to close', eSevr:=TcEventSeverity.Critical);
END_IF


//STATE Logger

IF ePrevState <> iq_stValve.eState THEN
      CASE iq_stValve.eState OF
            INVALID:
                    fbLogger(sMsg:='Valve invalid position.', eSevr:=TcEventSeverity.Critical);
            MOVING:
                    fbLogger(sMsg:='Valve moving', eSevr:=TcEventSeverity.Warning);
            OPEN:
                    fbLogger(sMsg:='Valve Open.', eSevr:=TcEventSeverity.Info);
            CLOSED:
                    fbLogger(sMsg:='Valve closed.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stValve.eState;
  END_IF



// Log valve timeouts
tErrorPresent(CLK:=iq_stValve.bErrorPresent);
IF tErrorPresent.Q THEN fbLogger(sMsg:=iq_stValve.sErrorMessage, eSevr:=TcEventSeverity.Warning); END_IF

// Log valve open
tAction(CLK:= iq_stValve.q_xOPN_DO);
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded open', eSevr:=TcEventSeverity.Info); END_IF

// Log override mode enabled
tOverrideActivated(CLK:= (tonOvrd.Q AND q_xOverrideMode));
IF tOverrideActivated.Q THEN fbLogger(sMsg:='Valve override mode activated', eSevr:=TcEventSeverity.Warning); END_IF
END_ACTION
ACTION IO:
(*inputs*)
iq_stValve.i_xTrigger := i_xTrigger;
iq_stValve.i_xOpnLS :=      i_xVFS_Open;
iq_stValve.i_xClsLS:=       i_xVFS_Closed;
iq_stValve.eVGC_State := i_eVFS_State;
iq_stValve.i_xVAC_FAULT_OK:= i_xVAC_FAULT_OK;
(*output*)
q_xOPN_SW := iq_stValve.pv_xOPN_SW;
q_xCLS_SW:= iq_stValve.xCLS_SW;
q_xVAC_FAULT_Reset :=  iq_stValve.pv_xAlmRst;
q_xOverrideMode :=iq_stValve.xOverrideMode;
q_xOverrideOpen := iq_stValve.pv_xOvrdOpn ;
iq_stValve.sGFS := IG.sPath;

//reset the commands
IF ( iq_stValve.i_xOpnLS) THEN iq_stValve.pv_xOPN_SW := FALSE; END_IF
IF ( iq_stValve.i_xClsLS) THEN iq_stValve.xCLS_SW := FALSE; END_IF
END_ACTION

FB_VGC

(* This function block implements basic functionality for Isolation Gate Valves*)
(* This function block interlock is as follows:
1. The valve can be opened when the difference between the pressures on both sides is
less than the maximum differential pressure.
2. This rule persists until the pressures on both sides are lower than the vacuum-setpoint.
3. Once at-vac, the valve will close if the pressure on either side rises above the setpoint.*)
(*This function block also implements PMPS and EPS interlocks, as well as Fast MPS trigger*)
{attribute 'no_check'}
{attribute 'reflection'}
FUNCTION_BLOCK FB_VGC Extends FB_Valve
VAR_IN_OUT

END_VAR
VAR_INPUT
    (*Upstream Gauge, usually ion gauge*)
    i_stUSG :       ST_VG;
    (*Downstream Gauge, usually ion gauge*)
    i_stDSG :       ST_VG;
    {attribute 'pytmc' := '
    pv: Dis_DPIlk
    '}
    i_xDis_DPIlk : BOOL := FALSE;  (* Set to true when calling the function to disable the differential pressure interlock *)
    i_xPMPS_OK:     BOOL    ; (*Set to True To switch off the bptm and PMPS Arbiter*)
    {attribute 'pytmc' := '
    pv: EPS_OK
    '}
    i_xEPS_OK:      BOOL    := TRUE; (*External EPS interlock, Set to TRUE when no EPS interlock is required, otherwise set to correct interlock signal*)
    i_xExt_OK: BOOL; (*Other External Interlock, Set to True when no external interlock is required. If this Valve is neigboring a Fast Shutter this should be linked to the fast shutter xVAC_FAULT_OK*)
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)
    // Reset fault
    {attribute 'pytmc' := '
    pv: FF_Reset
    '}
    i_xReset: BOOL;
    i_xIsAperture:BOOL :=FALSE; // Set tp True if this is an Aperture Valve, the MPS Fault will trip only when moving.
    i_sDevName : T_MaxString :=  'VGC'; // Device name for diagnostic
    i_nTransitionRootID: UDINT; //A unique transition Root ID that is equal to or greater than 1000
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stValve : ST_VGC; (* All valve data and states will be in this struct*)

    {attribute 'pytmc' := '
    pv: MPS_FAULT_OK
    field: ZNAM MPS FAULT ;
    field: ONAM MPS OK ;
    '}
    xMPS_OK:        BOOL; (*MPS Fast OK, is set when the Valve is Open*)
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
    fbArbiter: FB_Arbiter();
END_VAR
VAR
    // PMPS
    {attribute 'pytmc' := '
    pv: MPS_OK
    '}
    xPMPS_OK:       BOOL    ; (*PMPS interlock*)
    bMoving : BOOL;
    bDone :BOOL;
    tBPTMtimeout:TON;
    bptm: BeamParameterTransitionManager;
    FFO    :    FB_FastFault :=(
        i_DevName := 'VGC',
        i_Desc := 'Fault occurs when the valve is not in open state',
        i_TypeCode := 16#1010);
    //g_FastFaultOutput1    :       FB_HardwareFFOutput;

    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;

    rDiffPressAllowed       :       REAL := 22.5; // Torr, Default value comes from Vat Valve Manual
    rDiffPress : REAL;
    set : BOOL;
    reset: BOOL;

    xFirstPass      :       BOOL;
    fbFSInit                :       R_TRIG;

    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tonOvrd :       TON;
    rtOpen  :       R_TRIG;
    ftClose: F_TRIG;

    tDelOK  :       TIME := T#60S;
    tOvrd   :       TIME := T#10s;


    (* Timeouts*)
    tTimeOutDuration: TIME:= T#30S;
    tOPNtimeout: TON;
    tCLStimeout:TON;


    (*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xOPN_DO       AT%Q*: BOOL;

    // For logging
    eVGCPrevState : E_VGC;


END_VAR
(* Vacuum gate valve
A. Wallace
16-10-29

A gate valve isolates vacuum volumes. Ideally it can be opened when a system is vented
to allow for faster pumping, and will close when high vacuum is lost.

The following behavior is good for valves in something like a gas attenuator.
This function block does the following:
1. The valve can be opened when the difference between the pressures on both sides is
less than the maximum differential pressure.
2. This rule persists until the pressures on both sides are lower than the vacuum-setpoint.
3. Once at-vac, the valve will close if the pressure on either side rises above the setpoint.

Alternatively, the differential pressure interlock can be disabled so the valve may only be opened
if the pressure on both sides is lower than the at-vacuum-setpoint. You want this behavior if
the valve is to be used in a UHV section.

Hysteresis is employed to ensure a smooth transition from vented/pumping down, to at-vac.

Finally, an override system is built in so you can bypass all the interlocking logic and
get back online.
*)
(* 10/1/2018 Margaret Ghaly included EPS, PMPS Checkes and MPS trigger signals*)


fbFSInit( CLK := TRUE, Q => xFirstPass);
(*IO Mapping*)
ACT_IO();

(* On first PLC pass, put valve into vented state, which implies a closed valve *)
IF xFirstPass THEN
    iq_stValve.eVGC_State := Vented;
    iq_stValve.pv_xOPN_SW := FALSE;
    FFO.i_DevName := i_sDevName;
END_IF


///Check valve position
IF iq_stValve.i_xClsLS AND  iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=INVALID;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS AND iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN_F;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=CLOSED;
ELSIF NOT iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=MOVING;
ELSE
    iq_stValve.eState:=INVALID;
END_IF

// Update hysteresis
///////////////////////////////////////////////////
IF iq_stValve.rAT_VAC_SP_LAST <> iq_stValve.rAT_VAC_SP OR xFirstPass THEN
    iq_stValve.rAT_VAC_SP_LAST := iq_stValve.rAT_VAC_SP;
    iq_stValve.rAT_VAC_HYS := iq_stValve.rHYST_PERC * iq_stValve.rAT_VAC_SP;
END_IF

iq_stValve.rAT_VAC_HYS := LIMIT(0, iq_stValve.rAT_VAC_HYS, iq_stValve.rAT_VAC_SP);
IF iq_stValve.rAT_VAC_SP <> 0 THEN
    IF iq_stValve.rHYST_PERC <> (iq_stValve.rAT_VAC_HYS / iq_stValve.rAT_VAC_SP) THEN
            iq_stValve.rHYST_PERC := LIMIT(0, (iq_stValve.rAT_VAC_HYS / iq_stValve.rAT_VAC_SP) ,1);
    END_IF
END_IF
// Valve at vacuum check
///////////////////////////////////////////////////
    set := i_stUSG.rPRESS < iq_stValve.rAT_VAC_HYS AND i_stDSG.rPRESS < iq_stValve.rAT_VAC_HYS AND i_stUSG.xPRESS_OK AND i_stDSG.xPRESS_OK;
    iq_stValve.xAT_VAC S= set;
    reset := i_stUSG.rPRESS > iq_stValve.rAT_VAC_SP OR i_stDSG.rPRESS > iq_stValve.rAT_VAC_SP OR NOT i_stUSG.xPRESS_OK OR NOT i_stDSG.xPRESS_OK;
    iq_stValve.xAT_VAC R= reset;

// Differential pressure check
///////////////////////////////////////////////////
    (* Calc the differential pressure across the valve *)
    rDiffPress := ABS(i_stUSG.rPRESS - i_stDSG.rPRESS);

    (* As long as the differential pressure is less than 30mbar, the valve is allowed to open *)
    IF rDiffPress <= rDiffPressAllowed AND i_stUSG.xPRESS_OK AND i_stDSG.xPRESS_OK THEN
            iq_stValve.xDP_OK := TRUE;
    ELSE
            iq_stValve.xDP_OK       := FALSE;
            (* If the differential pressure is exceeded, even when the valve is open,
            the state is unexpected and triggers a fault, because it suggests that something
            is wrong with the valve, or gauges or mapping or there is a major leak on one side.*)
            iq_stValve.eVGC_State := ERR_DiffPress;
        iq_stValve.xERR_DifPres := TRUE;
    END_IF

// Valve state
///////////////////////////////////////////////////
CASE iq_stValve.eVGC_State OF
    Vented: (* The Vented state is used during pump down *)
            (* Assuming the pump down went well, we are now at vacuum on both sides,
            so we move to the vacuum state, otherwise remain in the vented state *)
            IF iq_stValve.xAT_VAC AND (NOT (iq_stValve.xERR_DifPres)) AND (NOT (iq_stValve.xERR_SP)) AND (NOT (iq_stValve.xERR_ExtFault))THEN
                iq_stValve.eVGC_State := AtVacuum;
            ELSE
                iq_stValve.eVGC_State := Vented;
            END_IF

    AtVacuum:
            IF iq_stValve.xAT_VAC THEN
            (* If both pressure setpoints are made,
            then enable the differential pressure interlock, regardless of the valve state
            assuming we're using some kind of ion gauge where a pressure setpoint cannot be higher than 1E-4 T *)
            iq_stValve.eVGC_State := AtVacuum;

            ELSE

                    (* If the valve is open (or in an unknown state) and either gauge is not at it's vacuum setpoint,
                    we have a loss of vacuum error *)
                    IF (iq_stValve.i_xOpnLS OR (NOT iq_stValve.i_xOpnLS AND NOT iq_stValve.i_xClsLS)) THEN
                            iq_stValve.eVGC_State := ERR_LostVac;
                            iq_stValve.xERR_SP := TRUE; //TAW changed this to the ERR_SP bool because I think that's what it was intended to be?

                    (* Alternatively, if the valve is already closed and we lose pressure on one side,
                    it was probably intentional venting, so we calmly move back to venting mode, and
                    disable the differential pressure interlock *) //??
                    ELSIF iq_stValve.i_xClsLS THEN
                            iq_stValve.eVGC_State := Vented;
                    END_IF

            END_IF

    ERR_DiffPress:
            IF NOT (iq_stValve.xERR_DifPres) THEN
                    IF iq_stValve.xAT_VAC AND (NOT (iq_stValve.xERR_DifPres)) AND (NOT (iq_stValve.xERR_SP)) AND (NOT (iq_stValve.xERR_ExtFault))THEN
                    iq_stValve.eVGC_State := AtVacuum;
                    ELSE
                    iq_stValve.eVGC_State := Vented;
                    END_IF
            END_IF


    ERR_LostVac:
            IF NOT (iq_stValve.xERR_SP) THEN
                    IF iq_stValve.xAT_VAC AND (NOT (iq_stValve.xERR_DifPres)) AND (NOT (iq_stValve.xERR_SP)) AND (NOT (iq_stValve.xERR_ExtFault))THEN
                    iq_stValve.eVGC_State := AtVacuum;
                    ELSE
                    iq_stValve.eVGC_State := Vented;
                    END_IF
            END_IF

    ERR_ExtFault:
            IF NOT (iq_stValve.xERR_ExtFault) AND (i_xExt_OK) THEN
                    IF iq_stValve.xAT_VAC AND (NOT (iq_stValve.xERR_DifPres)) AND (NOT (iq_stValve.xERR_SP)) AND (NOT (iq_stValve.xERR_ExtFault))THEN
                    iq_stValve.eVGC_State := AtVacuum;
                    ELSE
                    iq_stValve.eVGC_State := Vented;
                    END_IF
            END_IF
END_CASE

IF (NOT i_xExt_OK) AND (iq_stValve.eState=OPEN)  THEN
    iq_stValve.eVGC_State := ERR_ExtFault;
    iq_stValve.xERR_ExtFault := TRUE;
END_IF

// Interlock evaluation - with bypass for DP ILK bypass
///////////////////////////////////////////////
IF i_xDis_DPIlk THEN
    iq_stValve.xOPN_OK := iq_stValve.xEXT_OK; // AND iq_stValve.xDP_OK ;
ELSE
    iq_stValve.xOPN_OK := iq_stValve.xEXT_OK AND iq_stValve.xAT_VAC;
END_IF


(* Valve operation *)

(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xOPN_OK, PT:=tDelOK);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND iq_stValve.pv_xOvrdOpn THEN
    iq_stValve.pv_xOvrdOpn :=FALSE;
    if (iq_stValve.eState = OPEN) AND (i_xOverrideMode) THEN iq_stValve.pv_xOPN_SW := TRUE; END_IF //for seamless transition
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF
// Release the Force Open bit when the system override is false
IF NOT (i_xOverrideMode) THEN  iq_stValve.pv_xOvrdOpn :=FALSE; END_IF
//Override timer
tonOvrd(IN:=iq_stValve.pv_xOvrdOpn, PT:=tOvrd);


(* Valve operation *)
(* Here's where we evaluate sw actions and operate the valve *)
(* Valve operation *)

ftClose(CLK:= iq_stValve.pv_xOPN_SW);
rtOpen(CLK:= iq_stValve.pv_xOPN_SW);


(*PMPS*)
IF NOT(i_xPMPS_OK) THEN
    ACT_PMPS();
END_IF
(*when interlock is lost the Valve closes regardless of the status of the PMPS and EPS*)
xPMPS_OK := i_xPMPS_OK OR (bptm.q_xTransitionAuthorized OR tBPTMtimeout.Q);
IF NOT (iq_stValve.xOPN_OK) THEN
    iq_stValve.pv_xOPN_SW:= FALSE; // Reset switch after inlk is lost
    iq_stValve.q_xOPN_DO := (iq_stValve.pv_xOPN_SW  AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);
ELSIF xPMPS_OK THEN
    iq_stValve.q_xOPN_DO := (iq_stValve.pv_xOPN_SW  AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);
//ELSIF NOT (i_xPMPS_OK) THEN
    // Only override mode
    //iq_stValve.q_xOPN_DO := (tonOvrd.Q AND i_xOverrideMode);
END_IF


(*MPS Fault setting*)
If (i_xIsAperture ) THEN
    (* When the valve  is open or in Closed position MPS is OK, Fault while moving*)
    xMPS_OK := (i_xOpnLS  AND q_xOPN_DO) XOR (i_xClsLS  AND NOT q_xOPN_DO);
ELSE
    (* When the valve  is open MPS is OK*)
    xMPS_OK := i_xOpnLS AND NOT i_xClsLS AND q_xOPN_DO;
END_IF


///Check valve moving postion timout
IF NOT iq_stValve.i_xClsLS AND tCLStimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := 'Close Timeout';
ELSIF NOT iq_stValve.i_xOpnLS AND tOPNtimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := 'Open Timeout';
END_IF
IF (iq_stValve.eState=INVALID) THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := CONCAT(sPath,'Invalid Valve Position');
END_IF


(*Timers*)
tOPNtimeout(IN:= iq_stValve.q_xOPN_DO, PT := tTimeOutDuration );
tCLStimeout(IN:= NOT iq_stValve.q_xOPN_DO, PT := tTimeOutDuration);


// Alarm reset
//////////////////////////////////////
ACT_ResetAlarms();

(*IO Mapping*)
ACT_IO();

// Log States and triggers
ACT_Logger();



(*FAST FAULT*)
FFO(i_xOK := xMPS_OK,
    i_xReset := i_xReset,
    i_xAutoReset :=TRUE,
    io_fbFFHWO := io_fbFFHWO);

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.i_xOpnLS :=      i_xOpnLS;
iq_stValve.i_xClsLS:=       i_xClsLS;
iq_stValve.xEXT_OK := i_xEXT_OK;
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
q_xOPN_DO:= iq_stValve.q_xOPN_DO;

(*ILK Devices*)
iq_stValve.sIlkUSDeviceName := This^.i_stUSG.sPath;
iq_stValve.sIlkDSDeviceName := This^.i_stDSG.sPath;
END_ACTION
ACTION ACT_Logger:
// ILK logger

IF NOT (iq_stValve.xOPN_OK) AND (ePrevState = OPEN) AND NOT (TonOvrd.Q) THEN
            fbLogger(sMsg:='Lost interlock ok bit while valve was open.', eSevr:=TcEventSeverity.Critical);
END_IF


//Positiong STATE Logger
IF ePrevState <> iq_stValve.eState THEN
      CASE iq_stValve.eState OF
            INVALID:
                    fbLogger(sMsg:='Valve invalid position.', eSevr:=TcEventSeverity.Critical);
            MOVING:
                    fbLogger(sMsg:='Valve moving', eSevr:=TcEventSeverity.Warning);
            OPEN:
                    fbLogger(sMsg:='Valve Open.', eSevr:=TcEventSeverity.Info);
            OPEN_F:
                    fbLogger(sMsg:='Valve Open.', eSevr:=TcEventSeverity.Info);
            CLOSED:
                    fbLogger(sMsg:='Valve closed.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stValve.eState;
  END_IF

 //Pressure STATE Logger
IF eVGCPrevState <> iq_stValve.eVGC_State THEN
      CASE iq_stValve.eVGC_State OF
            Vented:
                     fbLogger(sMsg:='Vented', eSevr:=TcEventSeverity.Info);
            AtVacuum:
                     fbLogger(sMsg:='Vacuum setpoint satisfied', eSevr:=TcEventSeverity.Info);
            ERR_DiffPress:
                     fbLogger(sMsg:='Potential accidental vent.', eSevr:=TcEventSeverity.Critical);
            ERR_LostVac:
                    fbLogger(sMsg:='Unexpected loss of vacuum while valve was open or moving.', eSevr:=TcEventSeverity.Critical);
            ERR_ExtFault:
                     fbLogger(sMsg:='Lost external interlock while valve was open.', eSevr:=TcEventSeverity.Critical);
      END_CASE
      eVGCPrevState := iq_stValve.eVGC_State;
  END_IF



// Log valve timeouts
tErrorPresent(CLK:=iq_stValve.bErrorPresent);
IF tErrorPresent.Q THEN fbLogger(sMsg:=iq_stValve.sErrorMessage, eSevr:=TcEventSeverity.Warning); END_IF

// Log valve open
tAction(CLK:= iq_stValve.q_xOPN_DO);
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded open', eSevr:=TcEventSeverity.Info); END_IF

// Log override mode enabled
tOverrideActivated(CLK:= (tonOvrd.Q AND i_xOverrideMode));
IF tOverrideActivated.Q THEN fbLogger(sMsg:='Valve override mode activated', eSevr:=TcEventSeverity.Warning); END_IF
END_ACTION
ACTION ACT_PMPS:
bMoving := (iq_stValve.pv_xOPN_SW AND iq_stValve.i_xClsLS) XOR (NOT iq_stValve.pv_xOPN_SW AND i_xOpnLS);
bDone := (iq_stValve.pv_xOPN_SW AND iq_stValve.i_xOpnLS) XOR (NOT iq_stValve.pv_xOPN_SW AND i_xClsLS);
If (i_xIsAperture ) OR (iq_stValve.pv_xOPN_SW )THEN
    bptm.i_stRequestedAssertion := PMPS_GVL.cstFullBeam;
ELSE  bptm.i_stRequestedAssertion := PMPS_GVL.cst0RateBeam;
END_IF
bptm(fbArbiter:=fbArbiter,
     i_TransitionAssertionID:=i_nTransitionRootID+2,
     i_stTransitionAssertion:=PMPS_GVL.cst0RateBeam,
     i_nRequestedAssertionID:=i_nTransitionRootID+ BOOL_TO_UDINT( iq_stValve.pv_xOPN_SW),
     i_stRequestedAssertion:=,
     i_xMoving:=bMoving,
     i_xDoneMoving:= bDone,
     stCurrentBeamParameters:=PMPS_GVL.stCurrentBeamParameters,
     q_xTransitionAuthorized=>);
// Timeout
tBPTMtimeout(IN:= bMoving AND NOT bptm.q_xTransitionAuthorized , PT:=T#1S);
//xPMPS_OK := bptm.q_xTransitionAuthorized OR tBPTMtimeout.Q;

//Timeout and clear request
IF (tBPTMtimeout.Q) THEN
    fbArbiter.RemoveRequest(bptm.i_TransitionAssertionID);
    fbArbiter.RemoveRequest(bptm.i_nRequestedAssertionID);
END_IF
END_ACTION
ACTION ACT_ResetAlarms:
iq_stValve.xERR_DifPres R= iq_stValve.pv_xAlmRst;
iq_stValve.xERR_SP R= iq_stValve.pv_xAlmRst;
iq_stValve.bErrorPresent R= iq_stValve.pv_xAlmRst;
iq_stValve.xERR_ExtFault R= iq_stValve.pv_xAlmRst;

IF ( iq_stValve.pv_xAlmRst) THEN
    iq_stValve.sErrorMessage :='';
     iq_stValve.pv_xAlmRst := FALSE;
END_IF
END_ACTION

FB_VGC_AP

(*Deprectated*)
(* This function block implements basic functionality for Isolation Gate Valves with Appertures*)
(* This function block interlock is as follows:
1. The valve can be opened when the difference between the pressures on both sides is
less than the maximum differential pressure.
2. This rule persists until the pressures on both sides are lower than the vacuum-setpoint.
3. Once at-vac, the valve will close if the pressure on either side rises above the setpoint.*)
(*This function block also implements PMPS and EPS interlocks, as well as Fast MPS trigger*)
(* MPS is triggered differently than the VGC *)
{attribute 'no_check'}
FUNCTION_BLOCK FB_VGC_AP
VAR_IN_OUT

END_VAR
VAR_INPUT
    (*Upstream Gauge, usually ion gauge*)
    i_stUSG :       ST_VG;
    (*Downstream Gauge, usually ion gauge*)
    i_stDSG :       ST_VG;
    {attribute 'pytmc' := '
    pv: Dis_DPIlk
    '}
    i_xDis_DPIlk : BOOL := FALSE;  (* Set to true when calling the function to disable the differential pressure interlock *)
    {attribute 'pytmc' := '
    pv: EPS_OK
    '}
    i_xEPS_OK:      BOOL    := TRUE; (*External EPS interlock, Set to TRUE when no EPS interlock is required, otherwise set to correct interlock signal*)
    {attribute 'pytmc' := '
    pv: MPS_OK
    '}
    i_xPMPS_OK:     BOOL    ; (*External MPS interlock, Set to TRUE when no PMPS interlock is required*)
    i_xExt_OK: BOOL; (*Other External Interlock, Set to True when no external interlock is required. If this Valve is neigboring a Fast Shutter this should be linked to the fast shutter xVAC_FAULT_OK*)
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)
    // Reset fault
    {attribute 'pytmc' := '
    pv: FF_Reset
    '}
    i_xReset: BOOL;
    i_sDevName : T_MaxString :=  'VGC'; // Device name for diagnostic
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stValve : ST_VGC; (* All valve data and states will be in this struct*)

    {attribute 'pytmc' := '
    pv: MPS_FAULT_OK
    '}
    xMPS_OK:        BOOL; (*MPS Fast OK, is set when the Valve is Open*)
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR
    // PMPS
    fbFF    :    FB_FastFault :=(
        i_DevName := 'VGC Aptr',
        i_Desc := 'Fault occurs when the valve is not in open state',
        i_TypeCode := 16#1010);
    //g_FastFaultOutput1    :       FB_HardwareFFOutput;

    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;

    rDiffPressAllowed       :       REAL := 22.5; // Torr, Default value comes from Vat Valve Manual
    rDiffPress : REAL;
    set : BOOL;
    reset: BOOL;

    xFirstPass      :       BOOL;
    fbFSInit                :       R_TRIG;

    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tonOvrd :       TON;

    tDelOK  :       TIME := T#60S;
    tOvrd   :       TIME := T#10s;


    (* Timeouts*)
    tTimeOutDuration: TIME:= T#30S;
    tOPNtimeout: TON;
    tCLStimeout:TON;

     // For logging
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.VACUUM);
    ePrevState : E_VGC;
    tErrorPresent : R_TRIG;
    tAction : R_TRIG; // Primary action of this device (OPN_DO, PUMP_RUN, etc.)
    tOverrideActivated : R_TRIG;



    (*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xOPN_DO       AT%Q*: BOOL;

END_VAR
(* Vacuum gate valve
A. Wallace
16-10-29

A gate valve isolates vacuum volumes. Ideally it can be opened when a system is vented
to allow for faster pumping, and will close when high vacuum is lost.

The following behavior is good for valves in something like a gas attenuator.
This function block does the following:
1. The valve can be opened when the difference between the pressures on both sides is
less than the maximum differential pressure.
2. This rule persists until the pressures on both sides are lower than the vacuum-setpoint.
3. Once at-vac, the valve will close if the pressure on either side rises above the setpoint.

Alternatively, the differential pressure interlock can be disabled so the valve may only be opened
if the pressure on both sides is lower than the at-vacuum-setpoint. You want this behavior if
the valve is to be used in a UHV section.

Hysteresis is employed to ensure a smooth transition from vented/pumping down, to at-vac.

Finally, an override system is built in so you can bypass all the interlocking logic and
get back online.
*)
(* 10/1/2018 Margaret Ghaly included EPS, PMPS Checkes and MPS trigger signals*)


fbFSInit( CLK := TRUE, Q => xFirstPass);
(*IO Mapping*)
ACT_IO();

(* On first PLC pass, put valve into vented state, which implies a closed valve *)
IF xFirstPass THEN
    iq_stValve.eVGC_State := Vented;
    iq_stValve.pv_xOPN_SW := FALSE;
    fbFF.i_DevName := i_sDevName;
END_IF


///Check valve position
IF iq_stValve.i_xClsLS AND  iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=INVALID;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS AND iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN_F;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=CLOSED;
ELSIF NOT iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=MOVING;
ELSE
    iq_stValve.eState:=INVALID;
END_IF

// Update hysteresis
///////////////////////////////////////////////////
IF iq_stValve.rAT_VAC_SP_LAST <> iq_stValve.rAT_VAC_SP OR xFirstPass THEN
    iq_stValve.rAT_VAC_SP_LAST := iq_stValve.rAT_VAC_SP;
    iq_stValve.rAT_VAC_HYS := iq_stValve.rHYST_PERC * iq_stValve.rAT_VAC_SP;
END_IF

iq_stValve.rAT_VAC_HYS := LIMIT(0, iq_stValve.rAT_VAC_HYS, iq_stValve.rAT_VAC_SP);
IF iq_stValve.rAT_VAC_SP <> 0 THEN
    IF iq_stValve.rHYST_PERC <> (iq_stValve.rAT_VAC_HYS / iq_stValve.rAT_VAC_SP) THEN
            iq_stValve.rHYST_PERC := LIMIT(0, (iq_stValve.rAT_VAC_HYS / iq_stValve.rAT_VAC_SP) ,1);
    END_IF

END_IF
// Valve at vacuum check
///////////////////////////////////////////////////
    set := i_stUSG.rPRESS < iq_stValve.rAT_VAC_HYS AND i_stDSG.rPRESS < iq_stValve.rAT_VAC_HYS AND i_stUSG.xPRESS_OK AND i_stDSG.xPRESS_OK;
    iq_stValve.xAT_VAC S= set;
    reset := i_stUSG.rPRESS > iq_stValve.rAT_VAC_SP OR i_stDSG.rPRESS > iq_stValve.rAT_VAC_SP OR NOT i_stUSG.xPRESS_OK OR NOT i_stDSG.xPRESS_OK;
    iq_stValve.xAT_VAC R= reset;

// Differential pressure check
///////////////////////////////////////////////////
    (* Calc the differential pressure across the valve *)
    rDiffPress := ABS(i_stUSG.rPRESS - i_stDSG.rPRESS);

    (* As long as the differential pressure is less than 30mbar, the valve is allowed to open *)
    IF rDiffPress <= rDiffPressAllowed AND i_stUSG.xPRESS_OK AND i_stDSG.xPRESS_OK THEN
            iq_stValve.xDP_OK := TRUE;
    ELSE
            iq_stValve.xDP_OK       := FALSE;
            (* If the differential pressure is exceeded, even when the valve is open,
            the state is unexpected and triggers a fault, because it suggests that something
            is wrong with the valve, or gauges or mapping or there is a major leak on one side.*)
            iq_stValve.eVGC_State := ERR_DiffPress;
    END_IF

// Valve state
///////////////////////////////////////////////////
CASE iq_stValve.eVGC_State OF
    Vented: (* The Vented state is used during pump down *)
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Vented', eSevr:=TcEventSeverity.Info);
            ePrevState := iq_stValve.eVGC_State;
            END_IF

            (* Assuming the pump down went well, we are now at vacuum on both sides,
            so we move to the vacuum state, otherwise remain in the vented state *)
            IF iq_stValve.xAT_VAC AND (NOT (iq_stValve.xERR_DifPres)) AND  (NOT (iq_stValve.xERR_SP)) and  (NOT (iq_stValve.xERR_ExtFault))THEN
                iq_stValve.eVGC_State := AtVacuum;
            ELSE
                iq_stValve.eVGC_State := Vented;
            END_IF

    AtVacuum:
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Vacuum setpoint satisfied', eSevr:=TcEventSeverity.Info);
            ePrevState := iq_stValve.eVGC_State;
            END_IF

            IF iq_stValve.xAT_VAC THEN
            (* If both pressure setpoints are made,
            then enable the differential pressure interlock, regardless of the valve state
            assuming we're using some kind of ion gauge where a pressure setpoint cannot be higher than 1E-4 T *)
            iq_stValve.eVGC_State := AtVacuum;

            ELSE

                    (* If the valve is open (or in an unknown state) and either gauge is not at it's vacuum setpoint,
                    we have a loss of vacuum error *)
                    IF (iq_stValve.i_xOpnLS OR (NOT iq_stValve.i_xOpnLS AND NOT iq_stValve.i_xClsLS)) THEN
                            iq_stValve.eVGC_State := ERR_LostVac;
                            iq_stValve.xERR_SP := TRUE; //TAW changed this to the ERR_SP bool because I think that's what it was intended to be?

                    (* Alternatively, if the valve is already closed and we lose pressure on one side,
                    it was probably intentional venting, so we calmly move back to venting mode, and
                    disable the differential pressure interlock *) //??
                    ELSIF iq_stValve.i_xClsLS THEN
                            iq_stValve.eVGC_State := Vented;
                    END_IF

            END_IF
    ERR_DiffPress:
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Potential accidental vent.', eSevr:=TcEventSeverity.Critical);
            ePrevState := iq_stValve.eVGC_State;
            END_IF

            IF NOT (iq_stValve.xERR_DifPres) THEN
                    iq_stValve.eVGC_State := Vented;
            END_IF


    ERR_LostVac:
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Unexpected loss of vacuum while valve was open.', eSevr:=TcEventSeverity.Critical);
            ePrevState := iq_stValve.eVGC_State;
            END_IF
            IF NOT (iq_stValve.xERR_SP) THEN
                    iq_stValve.eVGC_State := Vented;
            END_IF

    ERR_ExtFault:
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Lost external interlock while valve was open.', eSevr:=TcEventSeverity.Critical);
            ePrevState := iq_stValve.eVGC_State;
            END_IF
            IF NOT (iq_stValve.xERR_ExtFault) AND (NOT i_xExt_OK) THEN
                    iq_stValve.eVGC_State := Vented;
            END_IF
    END_CASE



IF (NOT i_xExt_OK) THEN
    iq_stValve.eVGC_State := ERR_ExtFault;
    iq_stValve.xERR_ExtFault := TRUE;
END_IF

// Interlock evaluation - with bypass for DP ILK bypass
///////////////////////////////////////////////
IF i_xDis_DPIlk THEN
    iq_stValve.xOPN_OK := iq_stValve.xEXT_OK; // AND iq_stValve.xDP_OK ;
ELSE
    iq_stValve.xOPN_OK := iq_stValve.xEXT_OK AND iq_stValve.xAT_VAC;
END_IF


(* Valve operation *)

(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xOPN_OK, PT:=tDelOK);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND iq_stValve.pv_xOvrdOpn THEN
    iq_stValve.pv_xOvrdOpn :=FALSE;
    if (iq_stValve.eState = OPEN) AND (i_xOverrideMode) THEN iq_stValve.pv_xOPN_SW := TRUE; END_IF  //for seamless transition
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);

END_IF

//Override timer
tonOvrd(IN:=iq_stValve.pv_xOvrdOpn, PT:=tOvrd);

(* If Epics Command to close the Valve, check if PMPS and EPS are ok, otherwise Keep command set to open valve *)
IF NOT(iq_stValve.pv_xOPN_SW) AND ((NOT i_xPMPS_OK) OR (NOT i_xEPS_OK)) THEN
    //iq_stValve.pv_xOPN_SW := TRUE; // plc to only reset never to set this signal
END_IF
(* Reset the EPICS command to open the valve if the interlock is lost *)
(* based on EPS ok state, EPS overrides OPN_OK*)
IF NOT iq_stValve.xOPN_OK THEN
    IF i_xEPS_OK THEN iq_stValve.pv_xOPN_SW := FALSE; iq_stValve.sErrorMessage := 'ILK Active';
    ELSE //iq_stValve.pv_xOPN_SW := TRUE;           // plc to only reset never to set this signal
    END_IF
END_IF

(* Here's where the valve opens *)
iq_stValve.q_xOPN_DO := (iq_stValve.pv_xOPN_SW AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);

(* When the valve  is open MPS is OK*)
xMPS_OK := i_xOpnLS XOR i_xClsLS;

///Check valve moving postion timout
IF NOT iq_stValve.i_xClsLS AND tCLStimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := 'Close Timeout';
ELSIF NOT iq_stValve.i_xOpnLS AND tOPNtimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := 'Open Timeout';
END_IF
IF (iq_stValve.eState=INVALID) THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := CONCAT(sPath,'Invalid Valve Position');
END_IF
(*Timers*)
tOPNtimeout(IN:= iq_stValve.q_xOPN_DO, PT := tTimeOutDuration );
tCLStimeout(IN:= NOT iq_stValve.q_xOPN_DO, PT := tTimeOutDuration);


// Log valve timeouts
tErrorPresent(CLK:=iq_stValve.bErrorPresent);
IF tErrorPresent.Q THEN fbLogger(sMsg:=iq_stValve.sErrorMessage, eSevr:=TcEventSeverity.Warning); END_IF

// Log valve open
tAction(CLK:= iq_stValve.q_xOPN_DO);
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded open', eSevr:=TcEventSeverity.Info); END_IF

// Log override mode enabled
tOverrideActivated(CLK:= (tonOvrd.Q AND i_xOverrideMode));
IF tOverrideActivated.Q THEN fbLogger(sMsg:='Valve override mode activated', eSevr:=TcEventSeverity.Warning); END_IF


// Alarm reset
//////////////////////////////////////
ACT_ResetAlarms();

(*IO Mapping*)
ACT_IO();

(*FAST FAULT*)
fbFF(i_xOK := xMPS_OK,
    i_xReset := i_xReset,
    i_xAutoReset :=TRUE,
    io_fbFFHWO := io_fbFFHWO);

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.i_xOpnLS :=      i_xOpnLS;
iq_stValve.i_xClsLS:=       i_xClsLS;
iq_stValve.xEXT_OK := i_xEXT_OK;
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
q_xOPN_DO:= iq_stValve.q_xOPN_DO;
END_ACTION
ACTION ACT_ResetAlarms:
iq_stValve.xERR_DifPres R= iq_stValve.pv_xAlmRst;
iq_stValve.xERR_SP R= iq_stValve.pv_xAlmRst;
iq_stValve.bErrorPresent R= iq_stValve.pv_xAlmRst;

IF ( iq_stValve.pv_xAlmRst) THEN
    iq_stValve.sErrorMessage :='';
END_IF
END_ACTION

FB_VGC_EBD

(*Deprecated*)
(* This function block is similar to the FB_VGC, except it has 2 outputs to actuate the Valve*)
(* This function block implements basic functionality for Isolation Gate Valves*)
(* This function block interlock is as follows:
1. The valve can be opened when the difference between the pressures on both sides is
less than the maximum differential pressure.
2. This rule persists until the pressures on both sides are lower than the vacuum-setpoint.
3. Once at-vac, the valve will close if the pressure on either side rises above the setpoint.*)
(*This function block also implements PMPS and EPS interlocks, as well as Fast MPS trigger*)
{attribute 'no_check'}
FUNCTION_BLOCK FB_VGC_EBD
VAR_IN_OUT

END_VAR
VAR_INPUT
    (*Upstream Gauge, usually ion gauge*)
    i_stUSG :       ST_VG;
    (*Downstream Gauge, usually ion gauge*)
    i_stDSG :       ST_VG;
    {attribute 'pytmc' := '
    pv: Dis_DPIlk
    '}
    i_xDis_DPIlk : BOOL := FALSE;  (* Set to true when calling the function to disable the differential pressure interlock *)
    {attribute 'pytmc' := '
    pv: EPS_OK
    '}
    i_xEPS_OK:      BOOL    := TRUE; (*External EPS interlock, Set to TRUE when no EPS interlock is required, otherwise set to correct interlock signal*)
    {attribute 'pytmc' := '
    pv: MPS_OK
    '}
    i_xPMPS_OK:     BOOL    ; (*External MPS interlock, Set to TRUE when no PMPS interlock is required*)
    i_xExt_OK: BOOL; (*Other External Interlock, Set to True when no external interlock is required*)
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)
    // Reset fault
    {attribute 'pytmc' := '
    pv: FF_Reset
    '}
    i_xReset: BOOL;
    i_sDevName : T_MaxString :=  'VGC'; // Device name for diagnostic
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stValve : ST_VGC; (* All valve data and states will be in this struct*)

    {attribute 'pytmc' := '
    pv: MPS_FAULT_OK
    field: ZNAM MPS FAULT ;
    field: ONAM MPS OK ;
    '}
    xMPS_OK:        BOOL; (*MPS Fast OK, is set when the Valve is Open*)
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR
    // PMPS
    fbFF    :    FB_FastFault :=(
        i_DevName := 'VGC',
        i_Desc := 'Fault occurs when the valve is not in open state',
        i_TypeCode := 16#1010);
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;

    rDiffPressAllowed       :       REAL := 22.5; // Torr, Default value comes from Vat Valve Manual
    rDiffPress : REAL;
    set : BOOL;
    reset: BOOL;

    xFirstPass      :       BOOL;
    fbFSInit                :       R_TRIG;

    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tonOvrd :       TON;

    tDelOK  :       TIME := T#60S;
    tOvrd   :       TIME := T#10s;


    (* Timeouts*)
    tTimeOutDuration: TIME:= T#30S;
    tOPNtimeout: TON;
    tCLStimeout:TON;


     // For logging
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.VACUUM);
    ePrevState : E_VGC;
    tErrorPresent : R_TRIG;
    tAction : R_TRIG; // Primary action of this device (OPN_DO, PUMP_RUN, etc.)
    tOverrideActivated : R_TRIG;

    (*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xOPN_DO       AT%Q*: BOOL;
    q_xOPN_DO_2     AT%Q*: BOOL;

END_VAR
(* Vacuum gate valve
A. Wallace
16-10-29

A gate valve isolates vacuum volumes. Ideally it can be opened when a system is vented
to allow for faster pumping, and will close when high vacuum is lost.

The following behavior is good for valves in something like a gas attenuator.
This function block does the following:
1. The valve can be opened when the difference between the pressures on both sides is
less than the maximum differential pressure.
2. This rule persists until the pressures on both sides are lower than the vacuum-setpoint.
3. Once at-vac, the valve will close if the pressure on either side rises above the setpoint.

Alternatively, the differential pressure interlock can be disabled so the valve may only be opened
if the pressure on both sides is lower than the at-vacuum-setpoint. You want this behavior if
the valve is to be used in a UHV section.

Hysteresis is employed to ensure a smooth transition from vented/pumping down, to at-vac.

Finally, an override system is built in so you can bypass all the interlocking logic and
get back online.
*)
(* 10/1/2018 Margaret Ghaly included EPS, PMPS Checkes and MPS trigger signals*)


fbFSInit( CLK := TRUE, Q => xFirstPass);
(*IO Mapping*)
ACT_IO();

(* On first PLC pass, put valve into vented state, which implies a closed valve *)
IF xFirstPass THEN
    iq_stValve.eVGC_State := Vented;
    iq_stValve.pv_xOPN_SW := FALSE;
    fbFF.i_DevName := i_sDevName;
END_IF


///Check valve postion
IF iq_stValve.i_xClsLS AND  iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=INVALID;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS AND iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=CLOSED;
ELSIF NOT iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=MOVING;
ELSE
    iq_stValve.eState:=INVALID;
END_IF

// Update hysteresis
///////////////////////////////////////////////////
IF iq_stValve.rAT_VAC_SP_LAST <> iq_stValve.rAT_VAC_SP OR xFirstPass THEN
    iq_stValve.rAT_VAC_SP_LAST := iq_stValve.rAT_VAC_SP;
    iq_stValve.rAT_VAC_HYS := iq_stValve.rHYST_PERC * iq_stValve.rAT_VAC_SP;
END_IF

iq_stValve.rAT_VAC_HYS := LIMIT(0, iq_stValve.rAT_VAC_HYS, iq_stValve.rAT_VAC_SP);
IF iq_stValve.rAT_VAC_SP <> 0 THEN
    IF iq_stValve.rHYST_PERC <> (iq_stValve.rAT_VAC_HYS / iq_stValve.rAT_VAC_SP) THEN
            iq_stValve.rHYST_PERC := LIMIT(0, (iq_stValve.rAT_VAC_HYS / iq_stValve.rAT_VAC_SP) ,1);
    END_IF

END_IF
// Valve at vacuum check
///////////////////////////////////////////////////
    set := i_stUSG.rPRESS < iq_stValve.rAT_VAC_HYS AND i_stDSG.rPRESS < iq_stValve.rAT_VAC_HYS AND i_stUSG.xPRESS_OK AND i_stDSG.xPRESS_OK;
    iq_stValve.xAT_VAC S= set;
    reset := i_stUSG.rPRESS > iq_stValve.rAT_VAC_SP OR i_stDSG.rPRESS > iq_stValve.rAT_VAC_SP OR NOT i_stUSG.xPRESS_OK OR NOT i_stDSG.xPRESS_OK;
    iq_stValve.xAT_VAC R= reset;

// Differential pressure check
///////////////////////////////////////////////////
    (* Calc the differential pressure across the valve *)
    rDiffPress := ABS(i_stUSG.rPRESS - i_stDSG.rPRESS);

    (* As long as the differential pressure is less than 30mbar, the valve is allowed to open *)
    IF rDiffPress <= rDiffPressAllowed AND i_stUSG.xPRESS_OK AND i_stDSG.xPRESS_OK THEN
            iq_stValve.xDP_OK := TRUE;
    ELSE
            iq_stValve.xDP_OK       := FALSE;
            (* If the differential pressure is exceeded, even when the valve is open,
            the state is unexpected and triggers a fault, because it suggests that something
            is wrong with the valve, or gauges or mapping or there is a major leak on one side.*)
            iq_stValve.eVGC_State := ERR_DiffPress;
        iq_stValve.xERR_DifPres := TRUE;
    END_IF

// Valve state
///////////////////////////////////////////////////
CASE iq_stValve.eVGC_State OF
    Vented: (* The Vented state is used during pump down *)
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Vented', eSevr:=TcEventSeverity.Info);
            ePrevState := iq_stValve.eVGC_State;
            END_IF

            (* Assuming the pump down went well, we are now at vacuum on both sides,
            so we move to the vacuum state, otherwise remain in the vented state *)
            IF iq_stValve.xAT_VAC AND (NOT (iq_stValve.xERR_DifPres)) AND  (NOT (iq_stValve.xERR_SP)) and  (NOT (iq_stValve.xERR_ExtFault))THEN
                iq_stValve.eVGC_State := AtVacuum;
            ELSE
                iq_stValve.eVGC_State := Vented;
            END_IF

    AtVacuum:
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Vacuum setpoint satisfied', eSevr:=TcEventSeverity.Info);
            ePrevState := iq_stValve.eVGC_State;
            END_IF

            IF iq_stValve.xAT_VAC THEN
            (* If both pressure setpoints are made,
            then enable the differential pressure interlock, regardless of the valve state
            assuming we're using some kind of ion gauge where a pressure setpoint cannot be higher than 1E-4 T *)
            iq_stValve.eVGC_State := AtVacuum;

            ELSE

                    (* If the valve is open (or in an unknown state) and either gauge is not at it's vacuum setpoint,
                    we have a loss of vacuum error *)
                    IF (iq_stValve.i_xOpnLS OR (NOT iq_stValve.i_xOpnLS AND NOT iq_stValve.i_xClsLS)) THEN
                            iq_stValve.eVGC_State := ERR_LostVac;
                            iq_stValve.xERR_SP := TRUE; //TAW changed this to the ERR_SP bool because I think that's what it was intended to be?

                    (* Alternatively, if the valve is already closed and we lose pressure on one side,
                    it was probably intentional venting, so we calmly move back to venting mode, and
                    disable the differential pressure interlock *) //??
                    ELSIF iq_stValve.i_xClsLS THEN
                            iq_stValve.eVGC_State := Vented;
                    END_IF

            END_IF

    ERR_DiffPress:
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Potential accidental vent.', eSevr:=TcEventSeverity.Critical);
            ePrevState := iq_stValve.eVGC_State;
            END_IF

            IF NOT (iq_stValve.xERR_DifPres) THEN
                    iq_stValve.eVGC_State := Vented;
            END_IF


    ERR_LostVac:
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Unexpected loss of vacuum while valve was open.', eSevr:=TcEventSeverity.Critical);
            ePrevState := iq_stValve.eVGC_State;
            END_IF
            IF NOT (iq_stValve.xERR_SP) THEN
                    iq_stValve.eVGC_State := Vented;
            END_IF

    ERR_ExtFault:
        //Log new state
        IF ePrevState <> iq_stValve.eVGC_State THEN
            fbLogger(sMsg:='Lost external interlock while valve was open.', eSevr:=TcEventSeverity.Critical);
            ePrevState := iq_stValve.eVGC_State;
            END_IF
            IF NOT (iq_stValve.xERR_ExtFault) AND (NOT i_xExt_OK) THEN
                    iq_stValve.eVGC_State := Vented;
            END_IF
END_CASE

IF (NOT i_xExt_OK) THEN
    iq_stValve.eVGC_State := ERR_ExtFault;
    iq_stValve.xERR_ExtFault := TRUE;
END_IF

// Interlock evaluation - with bypass for DP ILK bypass
///////////////////////////////////////////////
IF i_xDis_DPIlk THEN
    iq_stValve.xOPN_OK := iq_stValve.xEXT_OK; // AND iq_stValve.xDP_OK ;
ELSE
    iq_stValve.xOPN_OK := iq_stValve.xEXT_OK AND iq_stValve.xAT_VAC;
END_IF


(* Valve operation *)

(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xOPN_OK, PT:=tDelOK);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND iq_stValve.pv_xOvrdOpn THEN
    iq_stValve.pv_xOvrdOpn :=FALSE;
    if (iq_stValve.eState = OPEN) AND (i_xOverrideMode) THEN iq_stValve.pv_xOPN_SW := TRUE; END_IF //for seamless transition
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);

END_IF

//Override timer
tonOvrd(IN:=iq_stValve.pv_xOvrdOpn, PT:=tOvrd);

(* If Epics Command to close the Valve, check if PMPS and EPS are ok, otherwise Keep command set to open valve *)
IF NOT(iq_stValve.pv_xOPN_SW) AND ((NOT i_xPMPS_OK) OR (NOT i_xEPS_OK)) THEN
    //iq_stValve.pv_xOPN_SW := TRUE; // plc to only reset never to set this signal
END_IF
(* Reset the EPICS command to open the valve if the interlock is lost *)
(* based on EPS ok state, EPS overrides OPN_OK*)
IF NOT iq_stValve.xOPN_OK THEN
    IF i_xEPS_OK THEN iq_stValve.pv_xOPN_SW := FALSE; iq_stValve.sErrorMessage := 'ILK Active';
    ELSE //iq_stValve.pv_xOPN_SW := TRUE;           // plc to only reset never to set this signal
    END_IF
END_IF

(* Here's where the valve opens *)
iq_stValve.q_xOPN_DO := (iq_stValve.pv_xOPN_SW AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);

(* When the valve  is open MPS is OK*)
xMPS_OK := (iq_stValve.eState=OPEN) OR (iq_stValve.eState=OPEN_F);//iq_stValve.i_xOpnLS;

///Check valve moving postion timout
IF NOT iq_stValve.i_xClsLS AND tCLStimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := ' Close Timeout';
ELSIF NOT iq_stValve.i_xOpnLS AND tOPNtimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := ' Open Timeout';
END_IF
IF (iq_stValve.eState=INVALID) THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := CONCAT(sPath,' Invalid Valve Position');
END_IF


(*Timers*)
tOPNtimeout(IN:= iq_stValve.q_xOPN_DO, PT := tTimeOutDuration );
tCLStimeout(IN:= NOT iq_stValve.q_xOPN_DO, PT := tTimeOutDuration);

// Log valve timeouts
tErrorPresent(CLK:=iq_stValve.bErrorPresent);
IF tErrorPresent.Q THEN fbLogger(sMsg:=iq_stValve.sErrorMessage, eSevr:=TcEventSeverity.Warning); END_IF

// Log valve open
tAction(CLK:= iq_stValve.q_xOPN_DO);
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded open', eSevr:=TcEventSeverity.Info); END_IF

// Log override mode enabled
tOverrideActivated(CLK:= (tonOvrd.Q AND i_xOverrideMode));
IF tOverrideActivated.Q THEN fbLogger(sMsg:='Valve override mode activated', eSevr:=TcEventSeverity.Warning); END_IF

// Alarm reset
//////////////////////////////////////
ACT_ResetAlarms();

(*IO Mapping*)
ACT_IO();

(*FAST FAULT*)
fbFF(i_xOK := xMPS_OK,
    i_xReset := i_xReset,
    i_xAutoReset :=TRUE,
    io_fbFFHWO := io_fbFFHWO);

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.i_xOpnLS :=      i_xOpnLS;
iq_stValve.i_xClsLS:=       i_xClsLS;
iq_stValve.xEXT_OK := i_xEXT_OK;
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
q_xOPN_DO:= iq_stValve.q_xOPN_DO;
q_xOPN_DO_2:= iq_stValve.q_xOPN_DO;


(*ILK Devices*)
iq_stValve.sIlkUSDeviceName := This^.i_stUSG.sPath;
iq_stValve.sIlkDSDeviceName := This^.i_stDSG.sPath;
END_ACTION
ACTION ACT_ResetAlarms:
iq_stValve.xERR_DifPres R= iq_stValve.pv_xAlmRst;
iq_stValve.xERR_SP R= iq_stValve.pv_xAlmRst;
iq_stValve.bErrorPresent R= iq_stValve.pv_xAlmRst;

IF ( iq_stValve.pv_xAlmRst) THEN
    iq_stValve.sErrorMessage :='';
END_IF
END_ACTION

FB_VGC_Test

FUNCTION_BLOCK FB_VGC_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    VGC: FB_VGC;
    US_IG : ST_VG;
    DS_IG : ST_VG;
    io_fbFFHWO    :    FB_HardwareFFOutput;
    TotalTests : INT:=0;
    ex               : __SYSTEM.ExceptionCode;
END_VAR
M_INIT();
M_Interlock();

END_FUNCTION_BLOCK

FB_VRC

(* This Function Block Implements Basic Functionality for certain types of valves e.g Turbo Isolation valves, Apperture Valve.
This function block is interloked by an input (i_xExtILK_OK). This is so developers can interface with custom
interlocking logic outside this function block.*)
(* Note Interlock Logic is External *)
{attribute 'no_check'}
FUNCTION_BLOCK FB_VRC EXTENDS FB_Valve
VAR_IN_OUT

END_VAR
VAR_INPUT
    i_xExtILK_OK : BOOL; //Connect to Interlock logic condition(e.g F_TURBO_VRC_ILK Function), otherwise, Set to True if the valve is not interlocked
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stValve : ST_VRC;
END_VAR
VAR
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
    xFirstPass      :       BOOL;
    fbFSInit                :       R_TRIG;
    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tOvrd   :       TIME := T#10s;
    (* Timeouts*)
    tTimeOutDuration: TIME:= T#30S;
    tOPNtimeout: TON;
    tCLStimeout:TON;


    (*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xOPN_DO       AT%Q*: BOOL;

END_VAR
(* On first PLC pass, put valve into vented state, which implies a closed valve *)
fbFSInit( CLK := TRUE, Q => xFirstPass);
IF xFirstPass THEN
    iq_stValve.eVGC_State := Vented;
    iq_stValve.pv_xOPN_SW := FALSE;
    ePrevState := INVALID;
END_IF




///Check valve position
IF iq_stValve.i_xClsLS AND  iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=INVALID;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS AND iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN_F;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=CLOSED;
ELSIF NOT iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=MOVING;
ELSE
    iq_stValve.eState:=INVALID;
END_IF


(*evaluate Valve open external interlock*)
iq_stValve.xOPN_OK := i_xExtILK_OK;
iq_stValve.xEXT_OK := i_xExtILK_OK;
IF NOT iq_stValve.xOPN_OK and NOT tonOvrd.Q THEN
    iq_stValve.pv_xOPN_SW := FALSE;
    iq_stValve.eVGC_State := ERR_ExtFault;
END_IF

IF (iq_stValve.q_xOPN_DO) AND (iq_stValve.xOPN_OK) THEN
    iq_stValve.eVGC_State := AtVacuum;
END_IF


(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xOPN_OK, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND iq_stValve.pv_xOvrdOpn THEN
    iq_stValve.pv_xOvrdOpn :=FALSE;
    if (iq_stValve.eState=OPEN) AND (i_xOverrideMode) THEN iq_stValve.pv_xOPN_SW := TRUE; END_IF
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF
// Release the Force Open bit when the system override is false
IF NOT(i_xOverrideMode) THEN  iq_stValve.pv_xOvrdOpn :=FALSE; END_IF
//Override timer
tonOvrd(IN:=iq_stValve.pv_xOvrdOpn, PT:=tOvrd);

(* Here's where the valve opens *)
iq_stValve.q_xOPN_DO := (iq_stValve.pv_xOPN_SW AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);


///Check valve moving position timout
IF NOT iq_stValve.i_xClsLS AND tCLStimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := ' Close Timeout';
ELSIF NOT iq_stValve.i_xOpnLS AND tOPNtimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := ' Open Timeout';
END_IF
IF (iq_stValve.eState=INVALID) THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := CONCAT(sPath,' Invalid Valve Position');
END_IF

(*Timers*)
tOPNtimeout(IN:= iq_stValve.q_xOPN_DO, PT := tTimeOutDuration );
tCLStimeout(IN:= NOT iq_stValve.q_xOPN_DO, PT := tTimeOutDuration);


(*Soft IO Mapping*)
ACT_IO();

// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.i_xOpnLS :=      i_xOpnLS;
iq_stValve.i_xClsLS:=       i_xClsLS;
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
q_xOPN_DO:= iq_stValve.q_xOPN_DO;
END_ACTION
ACTION ACT_Logger:
// ILK logger

IF NOT i_xExtILK_OK AND ePrevState = OPEN THEN
            fbLogger(sMsg:='Lost external interlock while valve was open.', eSevr:=TcEventSeverity.Critical);
END_IF


//STATE Logger

IF ePrevState <> iq_stValve.eState THEN
      CASE iq_stValve.eState OF
            INVALID:
                    fbLogger(sMsg:='Valve invalid position.', eSevr:=TcEventSeverity.Critical);
            MOVING:
                    fbLogger(sMsg:='Valve moving', eSevr:=TcEventSeverity.Warning);
            OPEN:
                    fbLogger(sMsg:='Valve Open.', eSevr:=TcEventSeverity.Info);
            CLOSED:
                    fbLogger(sMsg:='Valve closed.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stValve.eState;
  END_IF



// Log valve timeouts
tErrorPresent(CLK:=iq_stValve.bErrorPresent);
IF tErrorPresent.Q THEN fbLogger(sMsg:=iq_stValve.sErrorMessage, eSevr:=TcEventSeverity.Warning); END_IF

// Log valve open
tAction(CLK:= iq_stValve.q_xOPN_DO);
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded open', eSevr:=TcEventSeverity.Info); END_IF

// Log override mode enabled
tOverrideActivated(CLK:= (tonOvrd.Q AND i_xOverrideMode));
IF tOverrideActivated.Q THEN fbLogger(sMsg:='Valve override mode activated', eSevr:=TcEventSeverity.Warning); END_IF
END_ACTION

FB_VRC_EBD

(* This function block is similar to the FB_VGC, except it has 2 outputs to actuate the Valve*)
(* This Function Block Implements Basic Functionality for certain types of valves e.g Turbo Isolation valves, Apperture Valve *)
(* Note Interlock Logic is External *)
FUNCTION_BLOCK FB_VRC_EBD EXTENDS FB_Valve
VAR_IN_OUT

END_VAR
VAR_INPUT
    i_xExtILK_OK : BOOL; //Connect to Interlock logic condition(e.g F_TURBO_VRC_ILK Function), otherwise, Set to True if the valve is not interlocked
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stValve : ST_VRC;
END_VAR
VAR
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
    xFirstPass      :       BOOL;
    fbFSInit                :       R_TRIG;
    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tOvrd   :       TIME := T#10s;
    (* Timeouts*)
    tTimeOutDuration: TIME:= T#30S;
    tOPNtimeout: TON;
    tCLStimeout:TON;

    (*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xOPN_DO       AT%Q*: BOOL;
    q_xOPN_DO_2     AT%Q*: BOOL;
END_VAR
(* On first PLC pass, put valve into vented state, which implies a closed valve *)
fbFSInit( CLK := TRUE, Q => xFirstPass);
IF xFirstPass THEN
    iq_stValve.eVGC_State := Vented;
    iq_stValve.pv_xOPN_SW := FALSE;
END_IF


///Check valve position
IF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=OPEN;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=CLOSED;
ELSIF NOT iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=MOVING;
ELSE
    iq_stValve.eState:=INVALID;
END_IF

(*evaluate Valve open external interlock*)
iq_stValve.xOPN_OK := i_xExtILK_OK;
iq_stValve.xEXT_OK := i_xExtILK_OK;
IF NOT iq_stValve.xOPN_OK and NOT tonOvrd.Q THEN
    iq_stValve.pv_xOPN_SW := FALSE;
    iq_stValve.eVGC_State := ERR_ExtFault;
END_IF

IF (iq_stValve.q_xOPN_DO) AND (iq_stValve.xOPN_OK) THEN
    iq_stValve.eVGC_State := AtVacuum;
END_IF

(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xOPN_OK, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND iq_stValve.pv_xOvrdOpn THEN
    iq_stValve.pv_xOvrdOpn :=FALSE;
    if (iq_stValve.eState=OPEN) AND (i_xOverrideMode) THEN iq_stValve.pv_xOPN_SW := TRUE; END_IF
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF

//Override timer
tonOvrd(IN:=iq_stValve.pv_xOvrdOpn, PT:=T#10S);

(* Here's where the valve opens *)
iq_stValve.q_xOPN_DO := (iq_stValve.pv_xOPN_SW AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);

///Check valve moving postion timout
IF NOT iq_stValve.i_xClsLS AND tCLStimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := ' Close Timeout';
ELSIF NOT iq_stValve.i_xOpnLS AND tOPNtimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := ' Open Timeout';
END_IF
IF (iq_stValve.eState=INVALID) THEN
    iq_stValve.bErrorPresent := TRUE;
    iq_stValve.sErrorMessage := CONCAT(sPath,' Invalid Valve Position');
END_IF

(*Timers*)
tOPNtimeout(IN:= iq_stValve.q_xOPN_DO, PT := tTimeOutDuration );
tCLStimeout(IN:= NOT iq_stValve.q_xOPN_DO, PT := tTimeOutDuration);




(*Soft IO Mapping*)
ACT_IO();

// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.i_xOpnLS :=      i_xOpnLS;
iq_stValve.i_xClsLS:=       i_xClsLS;
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
q_xOPN_DO:= iq_stValve.q_xOPN_DO;
q_xOPN_DO_2:= iq_stValve.q_xOPN_DO;
END_ACTION
ACTION ACT_Logger:
// ILK logger

IF NOT i_xExtILK_OK AND ePrevState = OPEN THEN
            fbLogger(sMsg:='Lost external interlock while valve was open.', eSevr:=TcEventSeverity.Critical);
END_IF


//STATE Logger

IF ePrevState <> iq_stValve.eState THEN
      CASE iq_stValve.eState OF
            INVALID:
                    fbLogger(sMsg:='Valve invalid position.', eSevr:=TcEventSeverity.Critical);
            MOVING:
                    fbLogger(sMsg:='Valve moving', eSevr:=TcEventSeverity.Warning);
            OPEN:
                    fbLogger(sMsg:='Valve Open.', eSevr:=TcEventSeverity.Info);
            CLOSED:
                    fbLogger(sMsg:='Valve closed.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stValve.eState;
  END_IF



// Log valve timeouts
tErrorPresent(CLK:=iq_stValve.bErrorPresent);
IF tErrorPresent.Q THEN fbLogger(sMsg:=iq_stValve.sErrorMessage, eSevr:=TcEventSeverity.Warning); END_IF

// Log valve open
tAction(CLK:= iq_stValve.q_xOPN_DO);
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded open', eSevr:=TcEventSeverity.Info); END_IF

// Log override mode enabled
tOverrideActivated(CLK:= (tonOvrd.Q AND i_xOverrideMode));
IF tOverrideActivated.Q THEN fbLogger(sMsg:='Valve override mode activated', eSevr:=TcEventSeverity.Warning); END_IF
END_ACTION

FB_VRC_NO

(* This function block is different from the regular VRC in that CLOSING must be permitted. *)
FUNCTION_BLOCK FB_VRC_NO EXTENDS FB_Valve
VAR_IN_OUT

END_VAR
VAR_INPUT
    {attribute 'pytmc' := '
    pv: EXT_ILK_OK ;
    field: ZNAM NOT OK ;
    field: ONAM OK ;
    io: i ;
    '}
    i_xExtILK_OK : BOOL; //Connect to Interlock logic condition(e.g F_TURBO_VRC_ILK Function), otherwise, Set to True if the valve is not interlocked
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)

END_VAR
VAR_OUTPUT
            iq_stValve : ST_VRC;
END_VAR
VAR

    {attribute 'noinit'}
    sPath: STRING;
    xFirstPass      :       BOOL;
    fbFSInit                :       R_TRIG;
    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tOvrd   :       TIME := T#10s;
    (* Timeouts*)
    tTimeOutDuration: TIME:= T#30S;
    tOPNtimeout: TON;
    tCLStimeout:TON;
            {attribute 'pytmc' := '
    pv: CLS_SW ;
    field: ONAM CLOSE;
    field: ZNAM OPEN;
    io: io ;
    '}
    pv_xCLS_SW      : BOOL;
    {attribute 'pytmc' := '
    pv: FORCE_CLS;
    io: io;
    '}
    pv_xOvrdCls     :       BOOL;

(*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xCLS_DO       AT%Q*: BOOL;

END_VAR
(* This function block is different from the regular VRC in that CLOSING must be permitted. *)

(* On first PLC pass, put valve into vented state, which implies a closed valve *)
fbFSInit( CLK := TRUE, Q => xFirstPass);
IF xFirstPass THEN
    pv_xCLS_SW := FALSE;
END_IF




///Check valve position
IF iq_stValve.i_xClsLS AND  iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=INVALID;
ELSIF NOT iq_stValve.i_xClsLS AND iq_stValve.i_xOpnLS AND iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=CLOSED;
ELSIF NOT iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS THEN
    iq_stValve.eState:=MOVING;
ELSE
    iq_stValve.eState:=INVALID;
END_IF


(*evaluate Valve CLose external interlock*)
iq_stValve.xCLS_OK := i_xExtILK_OK;
IF NOT iq_stValve.xCLS_OK and NOT tonOvrd.Q THEN
    pv_xCLS_SW := FALSE;
END_IF


(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xCLS_OK, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND pv_xOvrdCls THEN
    pv_xOvrdCls :=FALSE;
    if (iq_stValve.eState=CLOSED) AND (i_xOverrideMode) THEN pv_xCLS_SW := TRUE; END_IF
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF

//Override timer
tonOvrd(IN:=pv_xOvrdCls, PT:=tOvrd);

(* Here's where the valve Actuates *)
q_xCLS_DO := ((iq_stValve.xCLS_OK  AND pv_xCLS_SW)OR (tonOvrd.Q AND i_xOverrideMode));
iq_stValve.q_xOPN_DO := NOT q_xCLS_DO;



///Check valve moving position timout
IF NOT iq_stValve.i_xClsLS AND tCLStimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    //iq_stValve.sErrorMessage := ' Close Timeout';
ELSIF NOT iq_stValve.i_xOpnLS AND tOPNtimeout.Q THEN
    iq_stValve.bErrorPresent := TRUE;
    //iq_stValve.sErrorMessage := ' Open Timeout';
END_IF
IF (iq_stValve.eState=INVALID) THEN
    iq_stValve.bErrorPresent := TRUE;
    //iq_stValve.sErrorMessage := CONCAT(sPath,' Invalid Valve Position');
END_IF

(*Timers*)
tOPNtimeout(IN:= iq_stValve.q_xOPN_DO, PT := tTimeOutDuration );
tCLStimeout(IN:= NOT iq_stValve.q_xOPN_DO, PT := tTimeOutDuration);


(*Soft IO Mapping*)
ACT_IO();

// Log States and triggers
ACT_Logger();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.i_xOpnLS :=      i_xOpnLS;
iq_stValve.i_xClsLS:=       i_xClsLS;
//iq_stValve. := i_xEXT_OK;
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
//q_xOPN_DO:= iq_stValve.q_xOPN_DO;
END_ACTION

FB_VRC_OK_CLS

(* This function block is different from the regular VRC in that CLOSING must be permitted. *)
FUNCTION_BLOCK FB_VRC_OK_CLS
VAR_IN_OUT
    iq_stValve : ST_VRC;
END_VAR
VAR_INPUT
    i_xOverrideMode : BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR

tonOvrd     :       TON;
tonDelOK : TON;
rtOK        :       R_TRIG;

END_VAR
(* This function block is different from the regular VRC in that CLOSING must be permitted. *)
(* Implemented for the funky valve 04 on LAMP *)
IF NOT iq_stValve.xOPN_OK and NOT tonOvrd.Q THEN
iq_stValve.pv_xOPN_SW := FALSE;
END_IF

(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xOPN_OK, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q THEN iq_stValve.pv_xOvrdOpn :=FALSE; END_IF

//Override timer
tonOvrd(IN:=iq_stValve.pv_xOvrdOpn, PT:=T#10S);

(* Here's where the valve opens *)
iq_stValve.q_xOPN_DO := NOT iq_stValve.xCLS_OK OR ((iq_stValve.pv_xOPN_SW AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode));

END_FUNCTION_BLOCK

FB_VRC_Test

FUNCTION_BLOCK FB_VRC_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    VRC: FB_VRC;
    ex               : __SYSTEM.ExceptionCode;
END_VAR
M_INIT();
M_Interlock();

END_FUNCTION_BLOCK

FB_VVC

(* This Function Block Implements Basic Functionality for Vent Valves VVC.

    This function block is also intended for the general use case of solenoid driven (non-variable) valves that lack status readbacks or position indicators.
*)
(* Note Interlock Logic is External *)
FUNCTION_BLOCK FB_VVC
VAR_IN_OUT

END_VAR
VAR_INPUT
    i_xExtILK_OK:BOOL; (*Other External Interlock, Set to True when no external interlock is required*)
    i_xOverrideMode : BOOL; (*To be linked to global override bit. This Overrides Vacuum logic only*)
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stValve : ST_VVC;
END_VAR
VAR

    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;

    (*IO*)
    q_xOPN_DO       AT%Q*: BOOL;

    pv_xOvrdOpn: INT;
END_VAR
iq_stValve.xOPN_OK := i_xExtILK_OK;

IF NOT iq_stValve.xOPN_OK THEN
iq_stValve.pv_xOPN_SW := FALSE;
END_IF

(* Override logic *)
(* Goal: give ability to override, but do so in a way that people won't forget it.
Solution: Override only after ten seconds of override, protect against blips,
when the valve permission goes true for more than ten seconds consistently, remove override
*)

tonDelOK(IN:=iq_stValve.xOPN_OK, PT:=T#10S);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q THEN iq_stValve.xOvrdOpn :=FALSE; END_IF
// Release the Force Open bit when the system override is false
IF NOT (i_xOverrideMode) THEN  iq_stValve.xOvrdOpn :=FALSE; END_IF

//Override timer
tonOvrd(IN:=iq_stValve.xOvrdOpn, PT:=T#10S);

(* Here's where the valve opens *)
iq_stValve.q_xOPN_DO := (iq_stValve.pv_xOPN_SW AND iq_stValve.xOPN_OK) OR (tonOvrd.Q AND i_xOverrideMode);

(*IO Mapping*)
ACT_IO();

END_FUNCTION_BLOCK
ACTION ACT_IO:
(*inputs*)
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
q_xOPN_DO:= iq_stValve.q_xOPN_DO;
END_ACTION

PRG_Test

PROGRAM PRG_Test
VAR
    fb_VGC_Test: FB_VGC_Test;
    fb_VRC_Test: FB_VRC_Test;
    fb_PIP_Test: FB_PIP_Test;
    fb_PTM_Test: FB_PTM_Test;
    fb_GPI_Test:FB_GPI_Test;
    fb_GCC_Test:FB_GCC_Test;

    TotalTests : INT:=0;
END_VAR
TcUnit.RUN();

END_PROGRAM

V_TO_INT

{attribute 'no_check'}
FUNCTION V_TO_INT : INT
VAR_INPUT
    analog : REAL;
END_VAR
VAR
    iBits :INT := 32767;
    iFullRange:INT := 10;
END_VAR
if ( iFullRange = 0 ) THEN iFullRange := 10;END_IF
V_TO_INT := REAL_TO_INT(analog * 32767 /iFullRange);

END_FUNCTION