DUTs

E_2OO3_MODE

TYPE E_2OO3_MODE :
(
    MODE_2OO3 := 0,
    IG1 := 1,
    IG2 := 2,
    IG3 := 3
);
END_TYPE
Related:

E_ControlMode

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_ControlMode :
(
    Init := 0,
    Synch := 1,
    ManualControl := 2,
    CloseValve := 3,
    OpenValve := 4,
    PressureControl := 5,
    HoldValve := 6,
    LearnProcedure := 7,
    InterlockOpen := 8,
    InterlockClose := 9,
    MaintenanceOpen := 10,
    MaintenanceClose := 11,
    PowerFailure := 12,
    Safety := 13,
    FatalError := 14
) USINT := Init;
END_TYPE

E_ControlModeSP

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_ControlModeSP :
(
    ManualControl := 2,
    CloseValve := 3,
    OpenValve := 4,
    PressureControl := 5,
    HoldValve := 6,
    LearnProcedure := 7
) USINT := CloseValve;
END_TYPE

E_FatalErrors

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_FatalErrors :
(
    NO_FATAL_ERROR := 0,
    NoStopDetectedSynch := 20,
    ValveBlockedSynch := 21,
    ValveBlocked := 22,
    StepLossSynch := 25,
    MotorDriverFault := 40
) UINT := NO_FATAL_ERROR;
END_TYPE

E_PressureState

TYPE E_PressureState :
(
    // Invalid states
    Off,//   //gc_GaugeValidState - 1,
    GaugeDisconnected, //gc_GaugeValidState -2,
    OoR, //gc_GaugeValidState -6,
    // Ion gauges
    PressInvalid, //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
Related:

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_VAT590_AccessMode

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_VAT590_AccessMode :
(
    LOCAL := 0,
    REMOTE := 1,
    LOCKED := 2
) USINT := REMOTE;
END_TYPE

E_VAT590_PID_CTRL

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_VAT590_PID_CTRL :
(
    ADAPTIVE_DS := 0,
    FIXED_1 := 1,
    FIXED_2 := 2,
    SOFT_PUMP := 3
) USINT := FIXED_1;
END_TYPE

E_VCN

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

    );
END_TYPE

E_VCN_VAT590

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_VCN_VAT590 :
(
    ManualControl := 0,
    CloseValve := 1,
    OpenValve := 2,
    PressureControl := 3,
    HoldValve := 4
) USINT := CloseValve;
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
Related:

ST_CoEIndSub

TYPE ST_CoEIndSub :
STRUCT
    nIndex : WORD;
    nSubIndex : BYTE;
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
Related:

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;


    {attribute 'pytmc' := '
    pv: RST_SW;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io: io;
    '}
    q_xReset        :       BOOL; //For resetting faults


END_STRUCT
END_TYPE
Related:

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

ST_ExtendedWarnings

TYPE ST_ExtendedWarnings :
STRUCT
    Bit0RemoteControlNotPossible: BIT;
    Bit1ControlModeSPNotAllowed : BIT;
    Bit2ZeroDisabled : BIT;
    Bit3PFODeactivated : BIT;
    Bit4Reserved : BIT;
    Bit5PressureSPOutOfRange : BIT;
    Bit6PositionSPOutOfRange : BIT;
    Bit7Reserved : BIT;
    Bit8Reserved : BIT;
    Bit9Reserved : BIT;
    Bit10CtrlSPOutOfRange : BIT;
    Bit11GeneralCtrlSPOutOfRange : BIT;
    Bit12ProcDataSettingsNotvalid : BIT;
    Bit13Reserved5 : BIT;
    Bit14Reserved6 : BIT;
    Bit15Reserved7 : BIT;
END_STRUCT
END_TYPE

ST_GeneralControlSP

TYPE ST_GeneralControlSP :
STRUCT
    Bit0ExecuteZero: BIT;
    Bit1Reserved : BIT;
    Bit2PingPongTxBit : BIT;
    Bit3Reserved : BIT;
    Bit4AccessMode : BIT;
    Bit5Reserved : BIT;
    Bit6Reserved : BIT;
    Bit7Reserved : BIT;
    Bit8Reserved : BIT;
    Bit9Reserved : BIT;
    Bit10Reserved : BIT;
    Bit11Reserved : BIT;
    Bit12Reserved : BIT;
    Bit13Reserved : BIT;
    Bit14Reserved : BIT;
    Bit15Reserved : BIT;
END_STRUCT
END_TYPE

ST_GeneralStatus

TYPE ST_GeneralStatus :
STRUCT
    Bit0FieldBusDatavalid: BIT;
    Bit1ZeroExecuted : BIT;
    Bit2PingPongRxBit : BIT;
    Bit3PressureSimulation : BIT;
    Bit4PressureSPReached : BIT;
    Bit5Reserved : BIT;
    Bit6Reserved : BIT;
    Bit7AccessModeBit0 : BIT;
    Bit8AccessModeBit1 : BIT;
    Bit9WarningsActive : BIT;
    Bit10Reserved : BIT;
    Bit11Reserved : BIT;
    Bit12Reserved : BIT;
    Bit13Reserved : BIT;
    Bit14Reserved : BIT;
    Bit15Reserved : BIT;
END_STRUCT
END_TYPE

ST_GeneralWarnings

TYPE ST_GeneralWarnings :
STRUCT
    Bit0ServiceRequest: BIT;
    Bit1Reserved : BIT;
    Bit2Reserved : BIT;
    Bit3PowerFailureBattery : BIT;
    Bit4Reserved : BIT;
    Bit5Reserved : BIT;
    Bit6Reserved : BIT;
    Bit7Reserved : BIT;
    Bit8Reserved : BIT;
    Bit9Reserved : BIT;
    Bit10SensMeasUnitFault : BIT;
    Bit11Reserved : BIT;
    Bit12Reserved : BIT;
    Bit13Reserved : BIT;
    Bit14Reserved : BIT;
    Bit15Reserved : BIT;
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_xRemote       :       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
Related:

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: PWR_RBV;
    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;
    i_xRemote : BOOL; (* Remote control enabled *)
END_STRUCT
END_TYPE
Related:

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: PWR_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;

    {attribute 'pytmc' := '
    pv: SET_SPEED;
    io: io;
    field: EGU "Hz"
    '}
    i_dSetSpd       :       DINT;

    {attribute 'pytmc' := '
    pv: SET_SPEED_MODE;
    io: io;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    '}
    q_bSetSpd       :       BOOL;

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

END_STRUCT
END_TYPE
Related:

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:io;
    '}
    xAutoOn : BOOL:=TRUE;

    {attribute 'pytmc' := '
    pv: AutoOn_timer;
    io:i;
    '}
    iAutoOnTimer : INT;

    {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
Related:

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

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

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

ST_VAT590_COE

TYPE ST_VAT590_COE :
STRUCT
    {attribute 'pytmc' := '
    pv: PID_CTRL
    io: io
    '}
    ePIDCtrl: E_VAT590_PID_CTRL;
    (*Adaptive Downstream Parameters*)
    {attribute 'pytmc' := '
    pv: SENS_DELAY_AD
    io: io
    field: EGU "s"
    field: DRVL 0.0
    field: DRVH 1.0
    '}
    fSensorDelay_AD : REAL;
    {attribute 'pytmc' := '
    pv: RAMP_TIME_AD
    io: io
    field: EGU "s"
    field: DRVL 0.0
    field: DRVH 1000000.0
    '}
    fRampTime_AD : REAL;
    {attribute 'pytmc' := '
    pv: RAMP_MODE_AD
    io: io
    field: ZNAM CONST_TIME
    field: ONAM CONST_SLOPE
    '}
    bRampMode_AD : BOOL;
    {attribute 'pytmc' := '
    pv: GAIN_FACTOR_AD
    io: io
    field: PREC 4
    field: DRVL 0.0001
    field: DRVH 7.5
    '}
    fGainFactor_AD : REAL;
    (*Fixed 1 Parameters*)
    {attribute 'pytmc' := '
    pv: RAMP_TIME_FIX1
    io: io
    field: EGU "s"
    field: DRVL 0.0
    field: DRVH 1000000.0
    '}
    fRampTime_FIX1 : REAL;
    {attribute 'pytmc' := '
    pv: RAMP_MODE_FIX1
    io: io
    field: ZNAM CONST_TIME
    field: ONAM CONST_SLOPE
    '}
    bRampMode_FIX1 : BOOL;
    {attribute 'pytmc' := '
    pv: CTRL_DIR_FIX1
    io: io
    field: ZNAM DOWNSTREAM
    field: ONAM UPSTREAM
    '}
    bCtrlDir_FIX1 : BOOL;
    {attribute 'pytmc' := '
    pv: PGAIN_FIX1
    io: io
    field: DRVL 0.001
    field: DRVH 100.0
    '}
    fPGain_FIX1 : REAL;
    {attribute 'pytmc' := '
    pv: IGAIN_FIX1
    io: io
    field: DRVL 0.0
    field: DRVH 100.0
    '}
    fIGain_FIX1 : REAL;
    (*Fixed 2 Parameters*)
    {attribute 'pytmc' := '
    pv: RAMP_TIME_FIX2
    io: io
    field: EGU "s"
    field: DRVL 0.0
    field: DRVH 1000000.0
    '}
    fRampTime_FIX2 : REAL;
    {attribute 'pytmc' := '
    pv: RAMP_MODE_FIX2
    io: io
    field: ZNAM CONST_TIME
    field: ONAM CONST_SLOPE
    '}
    bRampMode_FIX2 : BOOL;
    {attribute 'pytmc' := '
    pv: CTRL_DIR_FIX2
    io: io
    field: ZNAM DOWNSTREAM
    field: ONAM UPSTREAM
    '}
    bCtrlDir_FIX2 : BOOL;
    {attribute 'pytmc' := '
    pv: PGAIN_FIX2
    io: io
    field: DRVL 0.001
    field: DRVH 100.0
    '}
    fPGain_FIX2 : REAL;
    {attribute 'pytmc' := '
    pv: IGAIN_FIX2
    io: io
    field: DRVL 0.0
    field: DRVH 100.0
    '}
    fIGain_FIX2 : REAL;
    (*Soft Pump Parameters*)
    {attribute 'pytmc' := '
    pv: RAMP_TIME_SP
    io: io
    field: EGU "s"
    field: DRVL 0.0
    field: DRVH 1000000.0
    '}
    fRampTime_SP : REAL;
    {attribute 'pytmc' := '
    pv: RAMP_MODE_SP
    io: io
    field: ZNAM CONST_TIME
    field: ONAM CONST_SLOPE
    '}
    bRampMode_SP : BOOL;
    {attribute 'pytmc' := '
    pv: PGAIN_SP
    io: io
    field: DRVL 0.001
    field: DRVH 100.0
    '}
    fPGain_SP : REAL;
    (*Position Limit Parameters*)
    {attribute 'pytmc' := '
    pv: POSITION_LIMIT_OPN
    io: io
    field: EGU "%"
    field: DRVL 0.0
    field: DRVH 100.0
    '}
    fPositionLimitOpen : REAL;
    {attribute 'pytmc' := '
    pv: POSITION_LIMIT_OPN_CTRL
    io: io
    field: EGU "%"
    field: DRVL 0.0
    field: DRVH 100.0
    '}
    fPositionLimitOpenControl : REAL;
END_STRUCT
END_TYPE
Related:

ST_VAT590_STATUS

TYPE ST_VAT590_STATUS :
STRUCT
    {attribute 'pytmc' :=  '
            pv: CTRL_MODE
            io: i
    '}
    eControlMode : E_ControlMode; // Valve control mode readback
    {attribute 'pytmc' :=  '
            pv: FATAL_ERR
            io: i
    '}
    eFatalErrors : E_FatalErrors; // Valve Fatal Error status readback
    (*General Status readback*)
    {attribute 'pytmc' := '
            pv: GEN_STATUS:ECAT_DATA_VALID
            io: i
            field: ZNAM NOT_VALID
            field: ONAM VALID
            field: ZSV MAJOR
            field: OSV NO_ALARM
    '}
    bFieldBusDatavalid: BOOL;
    {attribute 'pytmc' := '
            pv: GEN_STATUS:ZERO_EXECUTED
            io: i
            field: ZNAM NOT_EXECUTED
            field: ONAM EXECUTED
            field: ZSV NO_ALARM
            field: OSV NO_ALARM
    '}
    bZeroExecuted : BOOL;
    {attribute 'pytmc' := '
            pv: GEN_STATUS:ECAT_RxBIT
            io: i
            field: ZNAM NOT_INVERTED
            field: ONAM INVERTED
            field: ZSV NO_ALARM
            field: OSV NO_ALARM
    '}
    bPingPongRxBit : BOOL;
    {attribute 'pytmc' := '
            pv: GEN_STATUS:PRES_SIM
            io: i
            field: ZNAM OFF
            field: ONAM ON
            field: ZSV NO_ALARM
            field: OSV NO_ALARM
    '}
    bPressureSimulation : BOOL;
    {attribute 'pytmc' := '
            pv: GEN_STATUS:PRES_SP_REACHED
            io: i
            field: ZNAM NOT_AT_SP
            field: ONAM AT_SP
            field: ZSV NO_ALARM
            field: OSV NO_ALARM
    '}
    bPressureSPReached : BOOL;
    {attribute 'pytmc' := '
            pv: GEN_STATUS:WARN_STATUS
            io: i
            field: ZNAM NOT_ACTIVE
            field: ONAM ACTIVE
            field: ZSV NO_ALARM
            field: OSV MAJOR
    '}
    bWarningsActive : BOOL;
    {attribute 'pytmc' :=  '
            pv: GEN_STATUS:REM_CTRL
            io: i
    '}
    eRemoteStatus : E_VAT590_AccessMode;
    (*General Warnings readback*)
    {attribute 'pytmc' := '
            pv: GEN_WARN:SERVICE_REQ
            io: i
            field: ZNAM OK
            field: ONAM SERVICE
            field: ZSV NO_ALARM
            field: OSV MAJOR
    '}
    bServiceRequest: BOOL;
    {attribute 'pytmc' := '
            pv: GEN_WARN:POWER_FAIL_BATT
            io: i
            field: ZNAM READY
            field: ONAM NOT_READY
            field: ZSV NO_ALARM
            field: OSV MINOR
    '}
    bPowerFailureBattery : BOOL;
    {attribute 'pytmc' := '
            pv: GEN_WARN:ADC_UNIT_STATUS
            io: i
            field: ZNAM OK
            field: ONAM FAULT
            field: ZSV NO_ALARM
            field: OSV MAJOR
    '}
    bSensMeasUnitFault : BOOL;
    (*Extended Warnings readback*)
    {attribute 'pytmc' := '
            pv: EXT_WARN:REM_NOT_POSSIBLE
            io: i
            field: ZNAM ENABLED
            field: ONAM DISABLED
            field: ZSV NO_ALARM
            field: OSV MINOR
    '}
    bRemoteControlNotPossible: BOOL;
    {attribute 'pytmc' := '
            pv: EXT_WARN:CTRL_SP_NOT_ALLOWED
            io: i
            field: ZNAM ENABLED
            field: ONAM DISABLED
            field: ZSV NO_ALARM
            field: OSV MINOR
    '}
    bControlModeSPNotAllowed : BOOL;
    {attribute 'pytmc' := '
            pv: EXT_WARN:ZERO_STATUS
            io: i
            field: ZNAM ENABLED
            field: ONAM DISABLED
            field: ZSV NO_ALARM
            field: OSV MINOR
    '}
    bZeroDisabled : BOOL;
    {attribute 'pytmc' := '
            pv: EXT_WARN:PFO_STATUS
            io: i
            field: ZNAM ENABLED
            field: ONAM DISABLED
            field: ZSV NO_ALARM
            field: OSV MINOR
    '}
    bPFODeactivated : BOOL;
    {attribute 'pytmc' := '
            pv: EXT_WARN:PRES_SP_OOR
            io: i
            field: ZNAM OK
            field: ONAM FAULT
            field: ZSV NO_ALARM
            field: OSV MAJOR
    '}
    bPressureSPOutOfRange : BOOL;
    {attribute 'pytmc' := '
            pv: EXT_WARN:POS_SP_OOR
            io: i
            field: ZNAM OK
            field: ONAM FAULT
            field: ZSV NO_ALARM
            field: OSV MAJOR
    '}
    bPositionSPOutOfRange : BOOL;
    {attribute 'pytmc' := '
            pv: EXT_WARN:CTRL_SP_OOR
            io: i
            field: ZNAM OK
            field: ONAM FAULT
            field: ZSV NO_ALARM
            field: OSV MAJOR
    '}
    bCtrlSPOutOfRange : BOOL;
    {attribute 'pytmc' := '
            pv: EXT_WARN:GENCTRL_SP_OOR
            io: i
            field: ZNAM OK
            field: ONAM FAULT
            field: ZSV NO_ALARM
            field: OSV MAJOR
    '}
    bGeneralCtrlSPOutOfRange : BOOL;
    {attribute 'pytmc' := '
            pv: EXT_WARN:PROC_DATA_NOT_VALID
            io: i
            field: ZNAM OK
            field: ONAM FAULT
            field: ZSV NO_ALARM
            field: OSV MAJOR
    '}
    bProcDataSettingsNotvalid : BOOL;
END_STRUCT
END_TYPE
Related:

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

    {attribute 'pytmc' := '
    pv: ILK_SP;
    io: o;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rIlk_SP :       REAL := 0.0005; // Interlock setpoint for gauges on both sides of valve

    {attribute 'pytmc' := '
    pv: Limit;
    io: io;
    autosave_pass1: VAL DESC
    '}
    rUpperLimit     :       REAL:=100; // Percentage//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
Related:

ST_VCN_VAT590

TYPE ST_VCN_VAT590 :
STRUCT
    (* VCN - Valve Controlled Needle
    Used for VAT590 needle valves *)
    {attribute 'pytmc' := '
    pv: POS_RAW
    io: i
    '}
    nRawPosition : DINT; //Position readback
    {attribute 'pytmc' := '
    pv: PRES_RAW
    io: i
    '}
    nRawPressure : DINT; //Pressure readback
    {attribute 'pytmc' := '
    pv: PRES_TORR
    field: EGU "TORR"
    io: i
    '}
    fPressure : REAL; //Pressure readback in Torr units
    {attribute 'pytmc' := '
    pv: POS_SP
    io: i
    '}
    nPositionSP : DINT; //Position setpoint readback
    {attribute 'pytmc' := '
    pv: PRES_SP
    io: i
    '}
    nPressureSP : DINT; //Pressure setpoint readback
    {attribute 'pytmc' := '
    pv: ILK_OK
    field: ZNAM NOT OK
    field: ONAM OK
    field: ZSV MAJOR
    field: OSV NO_ALARM
    io: i
    '}
    bIlkOK : BOOL := FALSE; //Interlock Bit status
    {attribute 'pytmc' := '
    pv: POS_LIMIT
    io: io
    autosave_pass1: VAL DESC
    '}
    fPosSetLimit    : REAL := 100; //Percentage Upper limit on valve open
    {attribute 'pytmc' := '
    pv: PRES_SET_LIMIT
    io: io
    autosave_pass1: VAL DESC
    '}
    fPresSetLimit   : REAL := 1E-2; //Upper limit for pressure control
    {attribute 'pytmc' := '
    pv: PRES_RDBK_LIMIT
    io: io
    autosave_pass1: VAL DESC
    '}
    fPresRdbkLimit : REAL := 1E-4;  //Upper limit for pressure reading before valve close
    {attribute 'pytmc' := '
    pv: POS_REQ
    field: DRVL 0.0
    field: DRVH 100.0
    io: io
    '}
    fReqPosition : REAL; //Requested position (0.0-100.0%)
    {attribute 'pytmc' := '
    pv: PRES_REQ
    field: EGU "TORR"
    io: io;
    '}
    fReqPressure : REAL; //Requested pressure in Torr units
    {attribute 'pytmc' :=  '
    pv: STATE
    io: io
    '}
    eValveControl : E_VCN_VAT590; //Valve control mode
    {attribute 'pytmc' := '
    pv:
    '}
    stValveStatus : ST_VAT590_STATUS; //All Valve status readbacks
    {attribute 'pytmc' :=  '
    pv: REM_CTRL
    io: io
    '}
    eRemoteControl : E_VAT590_AccessMode; //Select remote control mode
    {attribute 'pytmc' := '
    pv: ZERO
    io: io
    '}
    bZero : BOOL; //Activate Zero function bit
    {attribute 'pytmc' := '
    pv: COE_PARAM
    '}
    stCoeParameters : ST_VAT590_COE; //Coe Parameters
    {attribute 'pytmc' := '
    pv: COE_ERROR
    field: ZNAM OK
    field: ONAM ERROR
    field: ZSV NO_ALARM
    field: OSV MINOR
    io: i
    '}
    bCoeError : BOOL; //Coe Interface Error
    {attribute 'pytmc' := '
    pv: COE_RESET
    io: io
    '}
    bResetCoeError : BOOL; //Reset Coe Interface Error
    {attribute 'pytmc' := '
    pv: ETHCAT_COMM_STATUS
    field: ZNAM OK
    field: ONAM ERROR
    field: ZSV NO_ALARM
    field: OSV MAJOR
    io: i
    '}
    bValveEthCatStatus : BOOL; //Status of the Ethercat Communication with device
END_STRUCT
END_TYPE
Related:

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;

    {attribute 'pytmc' := '
    pv: VETO_DEVICE;
    io: i;
    '}
    sVetoDeviceName: STRING;

END_STRUCT
END_TYPE
Related:

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 Off;
    field: ONST GaugeDisconnected;
    field: TWST OoR;
    field: THST PressInvalid;
    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
    {attribute 'pytmc' := '
    pv: Auto_On;
    field: ZNAM FALSE;
    field: ONAM TRUE;
    io:io;
    '}
    xAutoOn : BOOL := TRUE;
    {attribute 'pytmc' := '
    pv: AutoOn_timer;
    io:i;
    '}
    iAutoOnTimer : INT;
    /// 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
Related:

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;
    sDevName :STRING;
(*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
Related:

ST_VGC_2S

TYPE ST_VGC_2S EXTENDS ST_VGC:
STRUCT
    {attribute 'pytmc' := '
    pv: AT_VAC_SP_DS;
    io: o;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rAT_VAC_SP_DS   :       REAL := 0.000001;       // Interlock setpoint for gauges on the downstream side of the valve
    {attribute 'pytmc' := '
    pv: AT_VAC_HYS_DS;
    io: o;
    field: HOPR 1000
    field: LOPR 0
    field: PREC 2
    field: EGU "TORR"
    autosave_pass1: VAL DESC
    '}
    rAT_VAC_HYS_DS  :       REAL :=0.000001;        // Hysteresis of the vacuum sp downstream
    rAT_VAC_SP_LAST_DS :    REAL :=0.000001 ;
END_STRUCT
END_TYPE
Related:

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

ST_VRC_NO

TYPE ST_VRC_NO EXTENDS ST_ValveBase:
STRUCT
(* Readbacks *)
    //In case VRC is normally open
    {attribute 'pytmc' := '
    pv: CLS_OK;
    field: ONAM TRUE;
    field: ZNAM FALSE;
    io: i;
    '}
    xCLS_OK : BOOL := TRUE;
    {attribute 'pytmc' := '
    pv: CLS_SW ;
    field: ONAM CLOSE;
    field: ZNAM OPEN;
    io: io ;
    '}
    pv_xCLS_SW      : BOOL;

    {attribute 'pytmc' := '
    pv: CLS_DO ;
    field: ONAM CLOSE;
    field: ZNAM OPEN;
    io: io ;
    '}
    q_xCLS_DO       : BOOL;
    {attribute 'pytmc' := '
    pv: FORCE_CLS;
    field: ONAM FORCE CLOSE;
    field: ZNAM FALSE;
    io: io;
    '}
    pv_xOvrdCls     :       BOOL;

END_STRUCT
END_TYPE
Related:

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 FORCE OPEN;
    io: io;
    '}
    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 *)
    {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

ST_VWC_NO

TYPE ST_VWC_NO EXTENDS ST_VGC:
STRUCT
(* Readbacks *)
    //In case VRC is normally open
    {attribute 'pytmc' := '
    pv: CLS_OK;
    field: ONAM CLOSE;
    field: ZNAM OPEN;
    io: i;
    '}
    xCLS_OK : BOOL := TRUE;
    {attribute 'pytmc' := '
    pv: CLS_SW ;
    field: ONAM CLOSE;
    field: ZNAM OPEN;
    io: io ;
    '}
    pv_xCLS_SW      : BOOL;
    {attribute 'pytmc' := '
    pv: FORCE_CLS;
    field: ONAM FORCE CLOSE;
    field: ZNAM FALSE;
    io: io;
    '}
    pv_xOvrdCls     :       BOOL;

END_STRUCT
END_TYPE
Related:

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;
     TaskInfo:PlcTaskSystemInfo;


END_VAR
Related:

Global_Version

{attribute 'TcGenerated'}
{attribute 'no-analysis'}
{attribute 'linkalways'}
// This function has been automatically generated from the project information.
VAR_GLOBAL CONSTANT
    {attribute 'const_non_replaced'}
    stLibVersion_LCLS_Vacuum : ST_LibVersion := (iMajor := 2, iMinor := 3, iBuild := 5, iRevision := 0, nFlags := 1, sVersion := '2.3.5');
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
Related:

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

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

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

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

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

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

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

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

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

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

FB_901_ECAT

(* works for 901 with Ethercat interface*)
{attribute 'no_check'}
FUNCTION_BLOCK FB_901_ECAT  EXTENDS FB_GaugeBase
VAR_INPUT
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    PG      : ST_VG;
END_VAR
VAR_IN_OUT

END_VAR
VAR
    bInit: BOOL:= TRUE;(*IO*)
    xPress_OK AT%I* : BOOL;
    xOverrange AT%I* : BOOL;
    xUnderrange AT%I* : BOOL;
    i_iPRESS_R AT%I* : REAL; // input Pressure // Link to analog Input
    bWcstate AT%I* : BOOL; // 0: date valid, 1: date invalid

END_VAR
(* Standard MKS 9XX series conversion *)
(* works for 901 with Ethercat interface*)
// no div by zero
PG.rPRESS :=i_iPRESS_R;
PG.xPRESS_OK := xPress_OK and NOT bWcstate ;
IF xPress_OK and NOT bWcstate THEN
    PG.xPRESS_OK := TRUE;
    PG.eState := Valid; //Press OK
ELSIF xOverrange and NOT bWcstate THEN
    PG.xPRESS_OK := FALSE;
    PG.eState := OoR;
ELSIF xUnderrange and NOT bWcstate THEN
    PG.xPRESS_OK := FALSE;
    PG.eState := PressInvalid;

END_IF



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

END_FUNCTION_BLOCK

ACTION ACT_Init:
IF (bInit) THEN
    bInit := False;

    //set pressure units to Torr
    // value 0x00A10000 SDO index 0xf840, si 0x01 UDINT
END_IF
END_ACTION

ACTION ACT_IO:
IF PG.xPRESS_OK THEN
    PG.i_iPRESS_R := REAL_TO_INT(i_iPRESS_R);
ELSE
    PG.rPRESS := 0.0;
END_IF
PG.sPath := sPath;
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
Related:

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) AND (rV <= 7) 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;
    IF (rV <= 1) THEN PG.eState := GaugeDisconnected;
            ELSE PG.eState := OoR;
    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;
PG.sPath := sPath;
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

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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
    rVMin : REAL:= 0.9;

    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

VAR CONSTANT
    (*Default set point 50 mT*)
    rDefaultVAC_SP: REAL := 5E-2;
END_VAR
(* Standard MKS 9XX series conversion *)
(* works for 972 and 925 *)


//Default setpoint
IF PG.rVAC_SP = 0 THEN
    PG.rVAC_SP := rDefaultVAC_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 >=rVMin THEN
    IF rV >= rVMin 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

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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_CCM501

(* This function is for the Cold Cathode CCM 501 fro instrutech (hornet). *)
(*This function provides ILK and Set Point Protection for the Cold Cathode*)

FUNCTION_BLOCK FB_CCM501 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
    tRecoverDelay:TIME:=T#600S; (*Delay Time after the first cycle to start the device. Default is 600S*)
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
    pv:
    '}
    IG : ST_VG;
END_VAR
VAR
    rV : REAL;
    GaugeTurnOnTmr : TON;
    tStartupTimer:TON;
    iTermBits: UINT := 30518 ; // 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
    binit:BOOL:=TRUE;
END_VAR

VAR CONSTANT
    // Ranges 1.8 to 8.7 Vdc, 0.8V/decade analog output - Torr
    vBase : REAL := 1.25;
    vDisconnected : REAL:= 1.8;
    vTorr : REAL := 12.875;
    vGaugeOff: REAL := 10.0;
    //vNoDischarge: REAL := 9.3;
    rMinPressure: REAL := 1E-10;
    cDefaultPressure : REAL := 0;
    rDeadband : REAL := 1.0;
END_VAR
VAR PERSISTENT
    bWasOn : BOOL;
    bAutoRecover:BOOL;
    bAutoRecoverWrite:BOOL;
END_VAR
(*Startup time*)
IF (bInit) THEN
    tStartupTimer.IN:=TRUE;
    bInit :=FALSE;
END_IF
tStartupTimer(PT:=T#3S);



If (iTermBits=0) THEN iTermBits := 30518;END_IF
rV := 10*INT_TO_REAL(IG.i_iPRESS_R)/iTermBits;

(* Set Guage State based on the Analog voltage*)

(* Set Guage State based on the Analog voltage*)

IF rV <= vDisconnected AND IG.i_xHV_ON and IG.q_xHV_DIS THEN
    IG.eState := ValidLo;
    IG.rPRESS := rMinPressure;
ELSIF rV <= vDisconnected AND Not IG.i_xHV_ON and IG.q_xHV_DIS THEN
    IG.eState := GaugeDisconnected;
    IG.rPRESS := cDefaultPressure;
ELSIF rV > vDisconnected AND rV < (vGaugeOff -rDeadband) AND IG.i_xHV_ON and IG.q_xHV_DIS   THEN
    IG.eState := Valid;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV*vBase)- vTorr)));
ELSIF rV >= (vGaugeOff -rDeadband) THEN
    IG.eState := Off;
    IG.rPRESS := cDefaultPressure;
ELSE
    IG.eState := OoR;
    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 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();
(*Load or save the persistent variables*)
ACT_Persistent();

(*Run Auto Recovery*)
ACT_Recover();

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
    tState(CLK:=  ePrevState <> IG.eState);
    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 ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)

IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF ( rVAC_SP <> 0) THEN
            IG.rVAC_SP := rVAC_SP;
    END_IF;
    IF ( rPRO_SP <> 0) THEN
            IG.rPRO_SP := rPRO_SP;
    END_IF;
    IF ( rHYS_PR <> 0) THEN
            IG.rHYS_PR := rHYS_PR;
    END_IF;
    IF ( bAutoRecoverWrite) THEN
            IG.xAutoOn := bAutoRecover;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (IG.rVAC_SP = rVAC_SP)  THEN
    rVAC_SP := IG.rVAC_SP;
END_IF;

IF NOT (IG.rPRO_SP = rPRO_SP) THEN
    rPRO_SP:= IG.rPRO_SP;
END_IF;

IF NOT (IG.rHYS_PR = rHYS_PR) THEN
    rHYS_PR:= IG.rHYS_PR;
END_IF;

IF NOT(bAutoRecoverWrite) OR NOT (bAutoRecover = IG.xAutoOn) THEN
    bAutoRecover := IG.xAutoOn;
    bAutoRecoverWrite := TRUE;
END_IF;
stateTimer(PT:=T#10S);
IF (stateTimer.Q) THEN
    bWasOn := (IG.eState >=Valid );
    stateTimer.IN := FALSE;
    fbWritePersistentData(NETID:='', PORT:=851, START:=TRUE, TMOUT:=T#1s );
END_IF;
END_ACTION

ACTION ACT_Recover:
fbGetCurTaskIdx();
IF (TwinCAT_SystemInfoVarList._TaskInfo[fbGetCurTaskIdx.index].FirstCycle) THEN
    tRecover.IN := TRUE;
END_IF
tRecover(IN:= , PT:=tRecoverDelay);
IF NOT(bAutoRecoverWrite) OR (tRecover.Q AND bAutoRecover ) THEN
    This^.M_Recover();
    tRecover.IN := FALSE;
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

METHOD M_HVE : BOOL
VAR_INPUT
            enable:bool; // set to true to enable, false to disable;
END_VAR
this^.IG.xHV_SW:= enable;
END_METHOD

METHOD M_Recover : BOOL
VAR_INPUT
END_VAR
This^.M_HVE(This^.bWasOn);
M_Recover := bWasOn;
IF (bWasOn) THEN fbLogger(sMsg:='Auto Recovery Initiated.', eSevr:=TcEventSeverity.Info); END_IF;
END_METHOD

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

FB_CVM201

(* 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 CVM 201  convectron SuperBee*)
{attribute 'no_check'}
FUNCTION_BLOCK FB_CVM201 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;
    (*Default set point 50 mT*)
    rDefaultVAC_SP: REAL := 5E-2;
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();

(*Load or save the persistent variables*)
ACT_Persistent();

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 ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rPRO_SP <> 0) THEN
            PG.rPRO_SP := rPRO_SP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (PG.rVAC_SP = rVAC_SP) THEN
    rVAC_SP:= PG.rVAC_SP;
END_IF;
END_ACTION

ACTION IO:
PG.i_iPRESS_R :=i_iPRESS_R;
PG.sPath := sPath;
END_ACTION

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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

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;
    stPump.q_xRemote := 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;
q_xResetAlarm:=stPump.q_xReset;
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
Related:

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

FB_GaugeBase

{attribute 'reflection'}
FUNCTION_BLOCK FB_GaugeBase
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;
     tState : R_TRIG;
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
    // For Persistent Data
    bRestorePersistentData  :       BOOL:=TRUE;
    stateTimer:TON;
    fbGetCurTaskIdx  : GETCURTASKINDEX;
    fbWritePersistentData           : WritePersistentData;
    tRecover: TON;
END_VAR
VAR PERSISTENT
    rVAC_SP :       REAL;
    rPRO_SP :       REAL;
    rHYS_PR: REAL;
END_VAR


END_FUNCTION_BLOCK
Related:

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

METHOD M_GetBit : BOOL
VAR_INPUT
    iIndex:INT;
    iBit:INT;
END_VAR
Case iBit OF
    nZero:
            M_GetBit:=i_PLC_nBits[iIndex].nZero;
    nOne:
            M_GetBit:=i_PLC_nBits[iIndex].nOne;
    nTwo:
            M_GetBit:=i_PLC_nBits[iIndex].nTwo;
    nThree:
            M_GetBit:=i_PLC_nBits[iIndex].nThree;
    nFour:
            M_GetBit:=i_PLC_nBits[iIndex].nFour;
    nFive:
            M_GetBit:=i_PLC_nBits[iIndex].nFive;
    nSix:
            M_GetBit:=i_PLC_nBits[iIndex].nSix;
    nSeven:
            M_GetBit:=i_PLC_nBits[iIndex].nSeven;
END_CASE
END_METHOD
Related:

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

METHOD M_CheckRange
VAR
    Expected:REAL;
    Actual:REAL;
    eStateExpected: E_PressureState;
    eStateActual: E_PressureState;
END_VAR
TEST('Check_Range');
Case This^.cycle of
    12:
    This^.i_iPRESS_R :=  V_TO_INT(0.61);
    This^.PG.rPRESS := 1E-4;
    This^.PG.xPRESS_OK := True;
    fb_MKS422.M_HVE(TRUE);

    15:
    TEST('MKS422_Range_937B');
    Expected :=1E-11 ;
    eStateExpected := Valid;
    Actual := This^.fb_MKS422.IG.rPRESS;
    eStateActual := This^.fb_MKS422.IG.eState;
    AssertTrue(This^.fb_MKS422.IG.xPRESS_OK, 'MKS422B xPRESS_OK is set to False');
    AssertEquals(eStateExpected,eStateActual,'MKS422B eState Fail');
    AssertEquals_REAL(Expected, Actual,1E-12, 'MKS422B rPress Fail');
    TEST_FINISHED_NAMED('MKS422_Range_937B');

    16:
    This^.i_iPRESS_R := V_TO_INT(2.4);
    This^.fb_MKS422.b937A:=TRUE;

    20:
    TEST('MKS422_Range_937A');
    Expected :=1E-8 ;
    Actual := This^.fb_MKS422.IG.rPRESS;
    eStateExpected := Valid;
    eStateActual := This^.fb_MKS422.IG.eState;
    AssertTrue(This^.fb_MKS422.IG.xPRESS_OK, 'MKS422A xPRESS_OK is set to False');
    AssertEquals(eStateExpected,eStateActual,'MKS422A eState Fail');
    AssertEquals_REAL(Expected, Actual,1E-9, 'MKS422A rPress Fail');
    TEST_FINISHED_NAMED('MKS422_Range_937A');


    21:
    This^.i_iPRESS_R :=  V_TO_INT(0.1);


    24:
    TEST('MKS422_Range_Off');
    Expected :=0 ;
    eStateExpected := GaugeDisconnected;
    Actual := This^.fb_MKS422.IG.rPRESS;
    eStateActual := This^.fb_MKS422.IG.eState;
    AssertFalse(This^.fb_MKS422.IG.xPRESS_OK, 'MKS422A xPRESS_OK is set to True');
    AssertEquals(eStateExpected,eStateActual,'MKS422A eState Fail');
    AssertEquals_REAL(Expected, Actual,1, 'MKS422A rPress Fail');
    TEST_FINISHED_NAMED('MKS422_Range_Off');


    25:
    This^.i_iPRESS_R :=  V_TO_INT(10);


    29:
    TEST('MKS422_Range_Invalid');
    Expected :=0 ;
    eStateExpected := PressInvalid;
    Actual := This^.fb_MKS422.IG.rPRESS;
    eStateActual := This^.fb_MKS422.IG.eState;
    AssertFalse(This^.fb_MKS422.IG.xPRESS_OK, 'MKS422A xPRESS_OK is set to True');
    AssertEquals(eStateExpected,eStateActual,'MKS422A eState Fail');
    AssertEquals_REAL(Expected, Actual,1, 'MKS422A rPress Fail');
    TEST_FINISHED_NAMED('MKS422_Range_Invalid');
    TEST_FINISHED_NAMED('Check_Range');

END_CASE

This^.fb_MKS422(PG:=This^.PG, IG=>);
END_METHOD

METHOD M_INIT
VAR
END_VAR
TEST('MKS422_Startup');
This^.PG.rPRESS := 1E-4;
This^.PG.xPRESS_OK := TRUE;
This^.fb_MKS422(PG:=This^.PG, IG=>);
IF( cycle = 3) THEN
    AssertFalse(This^.fb_MKS422.IG.xHV_SW, 'MKS422 xHV_SW set to True');
    AssertTrue(This^.fb_MKS422.IG.q_xHV_DIS, 'MKS422 q_xHV_DIS set to False');
    TEST_FINISHED_NAMED('MKS422_Startup');
End_IF;
END_METHOD

METHOD M_Interlock
VAR_INPUT
END_VAR
TEST('MKS422_Interlock_Active');
TEST('MKS422_Interlock_OK');
TEST('MKS422_Interlock_Lost');

Case cycle of
    1:
    This^.PG.rPRESS := 1E-1;
    This^.PG.xPRESS_OK := TRUE;
    fb_MKS422.M_HVE(TRUE);

    3:
    AssertFalse(This^.fb_MKS422.IG.xHV_SW, 'MKS422 xHV_SW set to True');
    AssertTrue(This^.fb_MKS422.IG.q_xHV_DIS, 'MKS422 q_xHV_DIS set to False');
    TEST_FINISHED_NAMED('MKS422_Interlock_Active');

    4:
    This^.PG.rPRESS := 1E-4;
    This^.PG.xPRESS_OK := True;
    fb_MKS422.M_HVE(TRUE);

    7:
    AssertTrue(This^.fb_MKS422.IG.xHV_SW, 'MKS422 xHV_SW set to False');
    AssertFalse(This^.fb_MKS422.IG.q_xHV_DIS, 'MKS422 q_xHV_DIS set to True');
    TEST_FINISHED_NAMED('MKS422_Interlock_OK');

    8:
    This^.PG.rPRESS := 1E-1;
    This^.PG.xPRESS_OK := False;
    This^.fb_MKS422(PG:=This^.PG, IG=>);

    11:
    AssertFalse(This^.fb_MKS422.IG.xHV_SW, 'MKS422 xHV_SW set to True');
    AssertTrue(This^.fb_MKS422.IG.q_xHV_DIS, 'MKS422 q_xHV_DIS set to False');
    TEST_FINISHED_NAMED('MKS422_Interlock_Lost');
END_CASE

This^.fb_MKS422(PG:=This^.PG, IG=>);
END_METHOD

METHOD M_SelfProtection
VAR_INPUT
END_VAR
// Switching the Cold cathode backon
TEST('MKS422_Interlock_OK_1');
This^.PG.rPRESS := 1E-4;
This^.PG.xPRESS_OK := True;
fb_MKS422.M_HVE(TRUE);
This^.fb_MKS422(PG:=This^.PG, IG=>);
AssertTrue(This^.fb_MKS422.IG.xHV_SW, 'MKS422 xHV_SW set to False');
AssertFalse(This^.fb_MKS422.IG.q_xHV_DIS, 'MKS422 q_xHV_DIS set to True');
TEST_FINISHED_NAMED('MKS422_Interlock_OK_1');

TEST('MKS422_Self_Protection');
This^.PG.rPRESS := 1E-3;
This^.PG.xPRESS_OK := TRUE;
This^.fb_MKS422(PG:=This^.PG, IG=>);
AssertFalse(This^.fb_MKS422.IG.xHV_SW, 'MKS422 xHV_SW set to True');
AssertTrue(This^.fb_MKS422.IG.q_xHV_DIS, 'MKS422 q_xHV_DIS set to False');
TEST_FINISHED_NAMED('MKS422_Self_Protection');
END_METHOD
Related:

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;
    PG.eState := Valid;
ELSE
    PG.xPRESS_OK := FALSE;
    PG.eState := PressInvalid;
END_IF

(*IO soft linking*)
ACT_IO();

END_FUNCTION_BLOCK

ACTION ACT_IO:
PG.i_iPRESS_R :=i_iPRESS_R;
END_ACTION

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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

METHOD M_CheckRange
VAR
    Expected:REAL;
    Actual:REAL;
    eStateExpected: E_PressureState;
    eStateActual: E_PressureState;
END_VAR
TEST('MKS275_Check_Range');
Case cycle of
    4:
    This^.i_iPRESS_R := V_TO_INT(3.674);
    Expected :=5 ;
    eStateExpected := Valid;

    7:
    TEST('MKS275_Range_R2');
    Actual := This^.fb_MKS275.PG.rPRESS;
    eStateActual := This^.fb_MKS275.PG.eState;
    AssertTrue(This^.fb_MKS275.PG.xPRESS_OK, 'MKS275 xPRESS_OK is set to False');
    AssertEquals(eStateExpected,eStateActual,'MKS275 eState Fail');
    AssertEquals_REAL(Expected, Actual,0.1, 'MKS275 rPress Fail');
    TEST_FINISHED_NAMED('MKS275_Range_R2');

    8:
    This^.i_iPRESS_R := V_TO_INT(5.614);
    Expected :=900 ;
    eStateExpected := Valid;


    12:
    TEST('MKS275_Range_R3');
    Actual := This^.fb_MKS275.PG.rPRESS;
    eStateActual := This^.fb_MKS275.PG.eState;
    AssertTrue(This^.fb_MKS275.PG.xPRESS_OK, 'MKS275 xPRESS_OK is set to False');
    AssertEquals(eStateExpected,eStateActual,'MKS275 eState Fail');
    AssertEquals_REAL(Expected, Actual,1, 'MKS275 rPress Fail');
    TEST_FINISHED_NAMED('MKS275_Range_R3');


    13:
    This^.i_iPRESS_R := V_TO_INT(0.3);
    Expected :=-1 ;
    eStateExpected := GaugeDisconnected;

    17:
    TEST('MKS275_Range_Off');
    Actual := This^.fb_MKS275.PG.rPRESS;
    eStateActual := This^.fb_MKS275.PG.eState;
    AssertTrue(This^.fb_MKS275.PG.xPRESS_OK, 'MKS275 xPRESS_OK is set to False');
    AssertEquals(eStateExpected,eStateActual,'MKS275 eState Fail');
    AssertEquals_REAL(Expected, Actual,1, 'MKS275 rPress Fail');
    TEST_FINISHED_NAMED('MKS275_Range_Off');


    18:
    This^.i_iPRESS_R := V_TO_INT(5.66);
    Expected :=-1 ;
    eStateExpected := OoR;

    22:
    TEST('MKS275_Range_OoR');
    Actual := This^.fb_MKS275.PG.rPRESS;
    eStateActual := This^.fb_MKS275.PG.eState;
    AssertTrue(This^.fb_MKS275.PG.xPRESS_OK, 'MKS275 xPRESS_OK is set to False');
    AssertEquals(eStateExpected,eStateActual,'MKS275 eState Fail');
    AssertEquals_REAL(Expected, Actual,1, 'MKS275 rPress Fail');
    TEST_FINISHED_NAMED('MKS275_Range_OoR');
    TEST_FINISHED_NAMED('MKS275_Check_Range');

END_CASE

This^.fb_MKS275(PG=>);
This^.fb_MKS317(PG=>);
END_METHOD

METHOD M_INIT
VAR
    Expected:REAL;
    Actual:REAL;
    eStateExpected: E_PressureState;
    eStateActual: E_PressureState;
END_VAR
// Start the Test
TEST('MKS275_Startup');
//Assign Voltage to FB
This^.i_iPRESS_R := V_TO_INT(0.384);
// Assigne expected result corresponing to the test input
Expected :=0.001 ;
eStateExpected := Valid;
//Instantiate the function block
This^.fb_MKS275(PG=>);
// Wait for few cycles
IF( cycle = 3) THEN
    // Check the results
    Actual := This^.fb_MKS275.PG.rPRESS;
    eStateActual := This^.fb_MKS275.PG.eState;
    AssertTrue(This^.fb_MKS275.PG.xPRESS_OK, 'MKS275 xPRESS_OK is set to False');
    AssertEquals(eStateExpected,eStateActual,'MKS275 eState Fail');
    AssertEquals_REAL(Expected, Actual,0.0001, 'MKS275 rPress Fail');
    TEST_FINISHED_NAMED('MKS275_Startup');
END_IF
END_METHOD
Related:

FB_IMG401

(* 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
IMG401-to-EP box interface because the EP boxes do not natively support the 5v IO
signals on the gauge.

This function provides ILK and Set Point Protection for the hot Cathode.
*)

FUNCTION_BLOCK FB_IMG401 EXTENDS FB_GaugeBase
VAR_IN_OUT

END_VAR
VAR_INPUT
    PG      :       ST_VG;
    tRecoverDelay:TIME:=T#600S; (*Delay Time after the first cycle to start the device. Default is 600S*)
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
    pv:
    '}
    IG : ST_VG;
END_VAR
VAR
    rV : REAL;
    GaugeTurnOnTmr : TON;
    tStartupTimer:TON;
    iTermBits: UINT := 30518 ; // 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' (EL1084) ^Input
//  i_xDisc_Active AT %I* : BOOL;// Discharge Current Active // 'TcLinkTo' (EL1124) ^Input
    binit:BOOL:=TRUE;
END_VAR

VAR CONSTANT
    vBase : REAL := 1.0;
    vDisconnected : REAL:= 0.001; //shift from 0 to eliminate single noise effect
    vTorr : REAL := 10;
    vGaugeOff: REAL := 10;
    //vNoDischarge: REAL := 9.3;
    rMinPressure: REAL := 1E-10;
    cDefaultPressure : REAL := 0;
    rDeadband : REAL :=1.0;
END_VAR

VAR PERSISTENT
    bWasOn : BOOL;
    bAutoRecover:BOOL;
    bAutoRecoverWrite:BOOL;
END_VAR
(*Startup time*)
IF (bInit) THEN
    tStartupTimer.IN:=TRUE;
    bInit :=FALSE;
END_IF
tStartupTimer(PT:=T#3S);

(* 401 Logarithmic Output Conversion, factory default configuration
   EL3174, EL3174-xxxx iTermbits 30518, 10V
*)
If (iTermBits=0) THEN iTermBits := 30518;END_IF
rV := 10*INT_TO_REAL(IG.i_iPRESS_R)/iTermBits;

(* Set Guage State based on the Analog voltage*)

IF rV <= vDisconnected AND IG.i_xHV_ON and IG.q_xHV_DIS THEN
    IG.eState := ValidLo;
    IG.rPRESS := rMinPressure;
ELSIF rV <= vDisconnected AND Not IG.i_xHV_ON and IG.q_xHV_DIS THEN
    IG.eState := GaugeDisconnected;
    IG.rPRESS := cDefaultPressure;
ELSIF rV > vDisconnected AND rV < (vGaugeOff -rDeadband) AND IG.i_xHV_ON and IG.q_xHV_DIS THEN
    IG.eState := Valid;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV*vBase)- vTorr)));
ELSIF rV >= (vGaugeOff -rDeadband) THEN
    IG.eState := Off;
    IG.rPRESS := cDefaultPressure;
ELSE
    IG.eState := OoR;
    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 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();
(*Load or save the persistent variables*)
ACT_Persistent();
(*Run Auto Recovery*)
ACT_Recover();

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
    tState(CLK:=  ePrevState <> IG.eState);
    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;
              stateTimer.IN := TRUE;
      END_IF
END_IF
END_ACTION

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF ( rVAC_SP <> 0) THEN
            IG.rVAC_SP := rVAC_SP;
    END_IF;
    IF ( rPRO_SP <> 0) THEN
            IG.rPRO_SP := rPRO_SP;
    END_IF;
    IF ( rHYS_PR <> 0) THEN
            IG.rHYS_PR := rHYS_PR;
    END_IF;
    IF ( bAutoRecoverWrite) THEN
            IG.xAutoOn := bAutoRecover;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (IG.rVAC_SP = rVAC_SP)  THEN
    rVAC_SP := IG.rVAC_SP;
END_IF;

IF NOT (IG.rPRO_SP = rPRO_SP) THEN
    rPRO_SP:= IG.rPRO_SP;
END_IF;

IF NOT (IG.rHYS_PR = rHYS_PR) THEN
    rHYS_PR:= IG.rHYS_PR;
END_IF;

IF NOT(bAutoRecoverWrite) OR NOT (bAutoRecover = IG.xAutoOn) THEN
    bAutoRecover := IG.xAutoOn;
    bAutoRecoverWrite := TRUE;
END_IF;
stateTimer(PT:=T#10S);
IF (stateTimer.Q) THEN
    bWasOn := (IG.eState >=Valid );
    stateTimer.IN := FALSE;
    fbWritePersistentData(NETID:='', PORT:=851, START:=TRUE, TMOUT:=T#1s );
END_IF;
END_ACTION

ACTION ACT_Recover:
fbGetCurTaskIdx();
IF (TwinCAT_SystemInfoVarList._TaskInfo[fbGetCurTaskIdx.index].FirstCycle) THEN
    tRecover.IN := TRUE;
END_IF
tRecover(IN:= , PT:=tRecoverDelay);
IF NOT(bAutoRecoverWrite) OR (tRecover.Q AND bAutoRecover ) THEN
    This^.M_Recover();
    tRecover.IN := FALSE;
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;
IG.iAutoOnTimer:= TIME_TO_INT(tRecover.PT);
END_ACTION

METHOD M_HVE : BOOL
VAR_INPUT
            enable:bool; // set to true to enable, false to disable;
END_VAR
this^.IG.xHV_SW:= enable;
END_METHOD

METHOD M_Recover : BOOL
VAR_INPUT
END_VAR
This^.M_HVE(This^.bWasOn);
M_Recover := bWasOn;
IF (bWasOn) THEN fbLogger(sMsg:='Auto Recovery Initiated.', eSevr:=TcEventSeverity.Info); END_IF;
END_METHOD

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
If (iTermBits=0) THEN iTermBits := 32767;END_IF
This^.iTermBits := TermBits;
END_METHOD
Related:

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

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_xRemote       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_xRemote 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_xRemote := i_xRemote;
    q_stPump.i_xAlarmOK :=  i_xAlarm;
    q_stPump.i_xWarningOK :=        i_xWarning;
    q_stPump.i_xIsRun :=    i_xIsRun;
END_ACTION

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.q_stPump.pv_xRunSW := run;
END_METHOD
Related:

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

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;
    (*Default set point 50 mT*)
    rDefaultVAC_SP: REAL := 5E-2;
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 := rDefaultVAC_SP;
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();

(*Load or save the persistent variables*)
ACT_Persistent();

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 ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rPRO_SP <> 0) THEN
            PG.rPRO_SP := rPRO_SP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (PG.rVAC_SP = rVAC_SP) THEN
    rVAC_SP:= PG.rVAC_SP;
END_IF;
END_ACTION

ACTION IO:
PG.i_iPRESS_R :=i_iPRESS_R;
PG.sPath := sPath;
END_ACTION

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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();

(*Load or save the persistent variables*)
ACT_Persistent();

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 ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rPRO_SP <> 0) THEN
            PG.rPRO_SP := rPRO_SP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (PG.rVAC_SP = rVAC_SP) THEN
    rVAC_SP:= PG.rVAC_SP;
END_IF;
END_ACTION

ACTION IO:
PG.i_iPRESS_R :=i_iPRESS_R;

PG.sPath := sPath;
END_ACTION
Related:

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 *)
{attribute 'obsolete' := 'FB_MKS317A has been deprecated and is not valid!'}
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
Related:

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

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
            tRecoverDelay:TIME:=T#600S; (*Delay Time after the first cycle to start the device. Default is 600S*)
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-11;
    vDisconnected : REAL:= 0.18;
    vMaxValid : REAL:=9.6;
    vMax : REAL:=9.9;
    vValidLo:REAL :=0.22;
    vMin : REAL:=0.6;
    cDefaultPressure : REAL := 0;
END_VAR
VAR PERSISTENT
    bWasOn : BOOL;
    bAutoRecover:BOOL;
    bAutoRecoverWrite:BOOL;
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 <= vMaxValid ) AND (rV>=vMin) AND (IG.xHV_SW) THEN
    IG.eState := Valid; // normal
ELSIF rV >= vDisconnected AND rV <= vValidLo AND (IG.xHV_SW) THEN
    IG.eState := ValidLo; //LO
ELSIF rV > vMaxValid AND rV<= vMax AND (IG.xHV_SW) THEN
    IG.eState := ValidHi; //HIGH
ELSIF rV < vDisconnected THEN //
    IG.eState := GaugeDisconnected; //other fault - gauge disconnected, controller powering up etc
    IG.rPRESS :=cDefaultPressure;
ELSIF NOT (IG.xHV_SW) THEN
    IG.eState := Off; //not on
    IG.rPRESS :=cDefaultPressure;
ELSIF IG.rPRESS < MinPressure THEN
    IG.eState := PressInvalid; //
    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 <=vMax ) AND (rV>=vMin);


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


(*Logger*)
ACT_Logger();
(*Soft IO Linking*)
// check ethercat Diagnostics
IO();
(*Load or save the persistent variables*)
ACT_Persistent();
timer(IN:= NOT IG.q_xHV_DIS, PT:= T#2s);
(*Run Auto Recovery*)
ACT_Recover();

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;
               stateTimer.IN := TRUE;
      END_IF
END_IF
END_ACTION

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF ( rVAC_SP <> 0) THEN
            IG.rVAC_SP := rVAC_SP;
    END_IF;
    IF ( rPRO_SP <> 0) THEN
            IG.rPRO_SP := rPRO_SP;
    END_IF;
    IF ( rHYS_PR <> 0) THEN
            IG.rHYS_PR := rHYS_PR;
    END_IF;
    IF ( bAutoRecoverWrite) THEN
            IG.xAutoOn := bAutoRecover;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (IG.rVAC_SP = rVAC_SP)  THEN
    rVAC_SP := IG.rVAC_SP;
END_IF;

IF NOT (IG.rPRO_SP = rPRO_SP) THEN
    rPRO_SP:= IG.rPRO_SP;
END_IF;

IF NOT (IG.rHYS_PR = rHYS_PR) THEN
    rHYS_PR:= IG.rHYS_PR;
END_IF;

IF NOT(bAutoRecoverWrite) OR NOT (IG.xAutoOn = bAutoRecover) THEN
    bAutoRecover := IG.xAutoOn;
    bAutoRecoverWrite := TRUE;
END_IF;
stateTimer(PT:=T#10S);
IF (stateTimer.Q) THEN
    bWasOn := (IG.eState >=Valid );
    stateTimer.IN := FALSE;
    fbWritePersistentData(NETID:='', PORT:=851, START:=TRUE, TMOUT:=T#1s );
END_IF;
END_ACTION

ACTION ACT_Recover:
fbGetCurTaskIdx();
IF (TwinCAT_SystemInfoVarList._TaskInfo[fbGetCurTaskIdx.index].FirstCycle) THEN
    tRecover.IN := TRUE;
END_IF
tRecover(IN:= , PT:=tRecoverDelay);
IF NOT(bAutoRecoverWrite) OR (tRecover.Q AND bAutoRecover ) THEN
    This^.M_Recover();
    tRecover.IN := FALSE;
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;
IG.iAutoOnTimer:= TIME_TO_INT(tRecover.PT);
END_ACTION

METHOD M_AutoOn : BOOL
VAR_INPUT
END_VAR
this^.IG.q_xHV_DIS:= FALSE;
END_METHOD

METHOD M_HVE : BOOL
VAR_INPUT
            enable:bool; // set to true to enable, false to disable;
END_VAR
this^.IG.xHV_SW:= (enable);
END_METHOD

METHOD M_Recover : BOOL
VAR_INPUT
END_VAR
This^.M_HVE(This^.bWasOn);
M_Recover := bWasOn;
IF (bWasOn) THEN fbLogger(sMsg:='Auto Recovery Initiated.', eSevr:=TcEventSeverity.Info); END_IF;
END_METHOD
Related:

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 because the EP boxes do not natively support the 5v IO
signals on the MKS500 gauge.

A setup process is required for the MKS500 to be usable.
See here: https://confluence.slac.stanford.edu/display/PCDS/MKS+500+-+Cold+Cathode

This function provides ILK and Set Point Protection for the Cold Cathode.
*)

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
    tRecoverDelay:TIME:=T#600S; (*Delay Time after the first cycle to start the device. Default is 600S*)
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
    pv:
    '}
    IG : ST_VG;
END_VAR
VAR
    rV : REAL;
    GaugeTurnOnTmr : TON;
    tStartupTimer: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
    binit:BOOL:=TRUE;
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
VAR PERSISTENT
    bWasOn : BOOL;
    bAutoRecover:BOOL;
    bAutoRecoverWrite:BOOL;
END_VAR
(*Startup time*)
IF (bInit) THEN
    tStartupTimer.IN:=TRUE;
    bInit :=FALSE;
END_IF
tStartupTimer(PT:=T#3S);

(* 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  NOT (tStartupTimer.Q) THEN // Give time for the gauge controller to start
    IG.eState := Off;
    IG.rPRESS := cDefaultPressure;
ELSIF rV < vDisconnected THEN
    IG.eState := GaugeDisconnected;
    IG.rPRESS := cDefaultPressure;
ELSIF rV >= vDisconnected AND rV < (vNoDischarge -rDeadband)  AND (IG.xHV_SW) THEN
    IG.eState := Valid;
    IG.rPRESS := LREAL_TO_REAL(EXPT(10,((rV-vBase)/vSlope+LOG(pBase))));
ELSIF rV >= (vNoDischarge -rDeadband) AND rV <(vGaugeOff -rDeadband) AND (IG.xHV_SW)  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;
ELSIF IG.rPRESS < MinPressure THEN
    IG.eState := PressInvalid;
    //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();
(*Load or save the persistent variables*)
ACT_Persistent();
(*Run Auto Recovery*)
ACT_Recover();

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
    tState(CLK:=  ePrevState <> IG.eState);
    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;
              stateTimer.IN := TRUE;
      END_IF
END_IF
END_ACTION

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF ( rVAC_SP <> 0) THEN
            IG.rVAC_SP := rVAC_SP;
    END_IF;
    IF ( rPRO_SP <> 0) THEN
            IG.rPRO_SP := rPRO_SP;
    END_IF;
    IF ( rHYS_PR <> 0) THEN
            IG.rHYS_PR := rHYS_PR;
    END_IF;
    IF ( bAutoRecoverWrite) THEN
            IG.xAutoOn := bAutoRecover;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (IG.rVAC_SP = rVAC_SP)  THEN
    rVAC_SP := IG.rVAC_SP;
END_IF;

IF NOT (IG.rPRO_SP = rPRO_SP) THEN
    rPRO_SP:= IG.rPRO_SP;
END_IF;

IF NOT (IG.rHYS_PR = rHYS_PR) THEN
    rHYS_PR:= IG.rHYS_PR;
END_IF;

IF NOT(bAutoRecoverWrite) OR NOT (bAutoRecover = IG.xAutoOn) THEN
    bAutoRecover := IG.xAutoOn;
    bAutoRecoverWrite := TRUE;
END_IF;
stateTimer(PT:=T#10S);
IF (stateTimer.Q) THEN
    bWasOn := (IG.eState >=Valid );
    stateTimer.IN := FALSE;
    fbWritePersistentData(NETID:='', PORT:=851, START:=TRUE, TMOUT:=T#1s );
END_IF;
END_ACTION

ACTION ACT_Recover:
fbGetCurTaskIdx();
IF (TwinCAT_SystemInfoVarList._TaskInfo[fbGetCurTaskIdx.index].FirstCycle) THEN
    tRecover.IN := TRUE;
END_IF
tRecover(IN:= , PT:=tRecoverDelay);
IF NOT(bAutoRecoverWrite) OR (tRecover.Q AND bAutoRecover ) THEN
    This^.M_Recover();
    tRecover.IN := FALSE;
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;
IG.iAutoOnTimer:= TIME_TO_INT(tRecover.PT);
END_ACTION

METHOD M_HVE : BOOL
VAR_INPUT
            enable:bool; // set to true to enable, false to disable;
END_VAR
this^.IG.xHV_SW:= enable;
END_METHOD

METHOD M_Recover : BOOL
VAR_INPUT
END_VAR
This^.M_HVE(This^.bWasOn);
M_Recover := bWasOn;
IF (bWasOn) THEN fbLogger(sMsg:='Auto Recovery Initiated.', eSevr:=TcEventSeverity.Info); END_IF;
END_METHOD

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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 *)
{attribute 'obsolete' := 'FB_MKS500_EP has been deprecated and is not valid!'}
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 *)
{attribute 'obsolete' := 'FB_VCC_NO has been deprecated and is not valid!'}
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

METHOD M_HVE : BOOL
VAR_INPUT
            enable:bool; // set to true to enable, false to disable;
END_VAR
this^.IG.xHV_SW:= NOT(enable);
END_METHOD
Related:

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

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*)
    rDefaultVAC_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 := rDefaultVAC_SP;
END_IF
// check no div by zero
If (iTermBits=0) THEN iTermBits := 30518;END_IF
rV := 10*INT_TO_REAL(IG.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
             IG.q_xHV_DIS := 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 := FALSE;
            IG.xHV_SW := FALSE;
            END_IF
    ELSE
            IG.q_xHV_DIS := FALSE;
            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();
(*Load or save the persistent variables*)
ACT_Persistent();

(*Logger*)
THIS^.ACT_Logger();

(*Load or save the persistent variables*)
ACT_Persistent();

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

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF ( rVAC_SP <> 0) THEN
            IG.rVAC_SP := rVAC_SP;
    END_IF;
    IF ( rPRO_SP <> 0) THEN
            IG.rPRO_SP := rPRO_SP;
    END_IF;
    IF ( rHYS_PR <> 0) THEN
            IG.rHYS_PR := rHYS_PR;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (IG.rVAC_SP = rVAC_SP)  THEN
    rVAC_SP := IG.rVAC_SP;
END_IF;

IF NOT (IG.rPRO_SP = rPRO_SP) THEN
    rPRO_SP:= IG.rPRO_SP;
END_IF;

IF NOT (IG.rHYS_PR = rHYS_PR) THEN
    rHYS_PR:= IG.rHYS_PR;
END_IF;
END_ACTION

METHOD M_AutoOn : BOOL
VAR_INPUT
END_VAR
this^.IG.q_xHV_DIS:= FALSE;
END_METHOD

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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

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

FB_MX7M

(* This function is for the Cold Cathode MX7M from Televac. *)
(*This function provides ILK and Set Point Protection for the Cold Cathode*)
FUNCTION_BLOCK FB_MX7M EXTENDS FB_GaugeBase
VAR_IN_OUT

END_VAR
VAR_INPUT
    PG : ST_VG;
    tRecoverDelay : TIME := T#600S; (*Delay Time after the first cycle to start the device. Default is 600S*)
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    IG : ST_VG;
END_VAR
VAR
    rV : REAL; //Calculated raw voltage from ADC on analog input
    rPress : REAL; //Calculated pressure
    tGaugeTurnOnTmr : TON;
    iTermBits : UINT := 30518 ; //The terminal's maximum value in bits

    (*IOs to be linked*)
    //Controls and I/Os
    i_iPRESS_R AT %I* : INT; //Pressure reading // 'TcLinkTo' (EL3174-0002 or EP3174-0002)^Input
    q_xHV_DIS AT %Q* : BOOL; //Enable High Voltage when True // 'TcLinkTo' (EL2624 or EP2624-0002)^Output
END_VAR
VAR CONSTANT
    //Ranges 1.0 to 10.0 Vdc (1E-11 to 1E-2) 1.0V to ~1.7V indicates that the pressure is lower than 5E-11(lowest value)
    //Must set the analog output scaling to logaritmic 2!!
    cSlope : REAL := 1.0;
    cBaseV : REAL := 1.0;
    cBaseP : REAL := 1E-11;
    cMinP : REAL := 5E-11;
    cGaugeMinV : REAL := 1.0;
    cGaugeLowV : REAL := 1.69;
    cGaugeMaxV : REAL := 10.0;
    cGaugeStartingV : REAL := 10.5;
    cDefaultP : REAL := 0.0;
    cDeadbandV : REAL := 0.1;
END_VAR
VAR PERSISTENT
    bWasOn : BOOL;
    bAutoRecover : BOOL;
    bAutoRecoverWrite : BOOL;
END_VAR
(*Calculate raw voltage*)
IF (iTermBits = 0) THEN iTermBits := 30518; END_IF
rV := 10*INT_TO_REAL(i_iPRESS_R)/iTermBits;

(*Calculate pressure*)
rPRESS := LREAL_TO_REAL(EXPT(10,((rV-cBaseV)/cSlope+LOG(cBaseP))));

(*Set Guage State based on the Analog voltage*)
IF IG.q_xHV_DIS THEN
    IF rV >= cGaugeLowV AND rV <= cGaugeMaxV THEN
            IG.eState := Valid;
        IG.rPRESS := rPRESS;
    ELSIF rV < cGaugeLowV AND rV >= cGaugeMinV - cDeadbandV THEN
            IG.eState := Valid;
        IG.rPRESS := cMinP;
    ELSIF rV > cGaugeStartingV - cDeadbandV THEN
        IG.eState := Starting;
        IG.rPRESS := cDefaultP;
    ELSIF rV < cGaugeMinV - cDeadbandV THEN
        IG.eState := GaugeDisconnected;
        IG.rPRESS := cDefaultP;
    ELSIF rV > cGaugeMaxV AND rV < cGaugeStartingV - cDeadbandV THEN
        IG.eState := OoR;
        IG.rPRESS := cDefaultP;
    ELSE
        IG.eState := PressInvalid;
        IG.rPRESS := cDefaultP;
    END_IF
ELSE
    IG.eState := Off;
    IG.rPRESS := cDefaultP;
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*)
tGaugeTurnOnTmr(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);

(*Logging*)
ACT_Logger();

(*Soft IO Linking*)
IO();

(*Load or save the persistent variables*)
ACT_Persistent();

(*Run Auto Recovery*)
ACT_Recover();

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 ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData := FALSE;
    IF (rVAC_SP <> 0) THEN
            IG.rVAC_SP := rVAC_SP;
    END_IF;
    IF (rPRO_SP <> 0) THEN
            IG.rPRO_SP := rPRO_SP;
    END_IF;
    IF (rHYS_PR <> 0) THEN
            IG.rHYS_PR := rHYS_PR;
    END_IF;
    IF (bAutoRecoverWrite) THEN
            IG.xAutoOn := bAutoRecover;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (IG.rVAC_SP = rVAC_SP)  THEN
    rVAC_SP := IG.rVAC_SP;
END_IF;

IF NOT (IG.rPRO_SP = rPRO_SP) THEN
    rPRO_SP := IG.rPRO_SP;
END_IF;

IF NOT (IG.rHYS_PR = rHYS_PR) THEN
    rHYS_PR := IG.rHYS_PR;
END_IF;

IF NOT(bAutoRecoverWrite) OR NOT (bAutoRecover = IG.xAutoOn) THEN
    bAutoRecover := IG.xAutoOn;
    bAutoRecoverWrite := TRUE;
END_IF;

stateTimer(PT := T#10S);
IF (stateTimer.Q) THEN
    bWasOn := (IG.eState >= Valid);
    stateTimer.IN := FALSE;
    fbWritePersistentData(NETID:='', PORT:=851, START:=TRUE, TMOUT:=T#1s);
END_IF;
END_ACTION

ACTION ACT_Recover:
fbGetCurTaskIdx();
IF (TwinCAT_SystemInfoVarList._TaskInfo[fbGetCurTaskIdx.index].FirstCycle) THEN
    tRecover.IN := TRUE;
END_IF
tRecover(PT := tRecoverDelay);
IF NOT(bAutoRecoverWrite) OR (tRecover.Q AND bAutoRecover) THEN
    This^.M_Recover();
    tRecover.IN := FALSE;
END_IF
END_ACTION

ACTION IO:
(*soft link inputs*)
IG.i_iPRESS_R := i_iPRESS_R;
IG.i_xHV_ON := IG.q_xHV_DIS;  //not a real readback, assuming turning on HV output, HV status is ON and vice versa

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

(*Gauge name link*)
IG.sPath := sPath;
END_ACTION

METHOD M_HVE : BOOL
VAR_INPUT
            bEnable : BOOL; // set to true to enable, false to disable;
END_VAR
this^.IG.xHV_SW := bEnable;
END_METHOD

METHOD M_Recover : BOOL
VAR_INPUT
END_VAR
This^.M_HVE(This^.bWasOn);
M_Recover := bWasOn;
IF (bWasOn) THEN fbLogger(sMsg:='Auto Recovery Initiated.', eSevr:=TcEventSeverity.Info); END_IF;
END_METHOD

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD
Related:

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*)
    tRecoverDelay:TIME:=T#900S; (*Delay Time after the first cycle to start the device. Default is 900S*)
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
 rPRESS : REAL;

 rV : REAL;
 timer:TON;

 (* 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
stateTimer:TON;
//Overrides
    tonOvrd :       TON;
    tonDelOK : TON;
    rtOK    :       R_TRIG;
    tOvrd   :       TIME := T#10s;

    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
    fbGetCurTaskIdx  : GETCURTASKINDEX;
    fbWritePersistentData           : WritePersistentData;
    tRecover: TON;
END_VAR
VAR CONSTANT
     rDefaultHVEna_SP : REAL :=1E-4; // Default protection setpoint as per the gamma QPCe manual
END_VAR
VAR PERSISTENT
    rHVEna_SP : REAL;
    bWasOn : BOOL;
    bAutoRecover:BOOL;
    bAutoRecoverWrite:BOOL;
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 > rDefaultHVEna_SP) THEN
    stPump.rHVEna_SP := rDefaultHVEna_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
    stPump.q_xHVEna_DO := stPump.xHVEna_SW OR ((tonOvrd.Q AND i_xOverrideMode));
    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();
(*Load or save the persistent variables*)
ACT_Persistent();
(*Run Auto Recovery*)
ACT_Recover();

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;
      stateTimer.IN := TRUE;
  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_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rHVEna_SP <> 0) THEN
            stPump.rHVEna_SP := rHVEna_SP;
    END_IF;
    IF ( bAutoRecoverWrite) THEN
            stPump.xAutoOn := bAutoRecover;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (stPump.rHVEna_SP = rHVEna_SP) THEN
    rHVEna_SP := stPump.rHVEna_SP;
END_IF;
IF NOT(bAutoRecoverWrite) OR NOT (stPump.xAutoOn = bAutoRecover) THEN
    bAutoRecover := stPump.xAutoOn;
    bAutoRecoverWrite := TRUE;
END_IF;
stateTimer(PT:=T#10S);
IF (stateTimer.Q) THEN
    bWasOn := (stPump.eState = pumpRUNNING);
    stateTimer.IN := FALSE;
    fbWritePersistentData(NETID:='', PORT:=851, START:=TRUE, TMOUT:=T#1s );
END_IF;
END_ACTION

ACTION ACT_Recover:
fbGetCurTaskIdx();
IF (TwinCAT_SystemInfoVarList._TaskInfo[fbGetCurTaskIdx.index].FirstCycle) THEN
    tRecover.IN := TRUE;
END_IF
tRecover(IN:= , PT:=tRecoverDelay);
IF NOT(bAutoRecoverWrite) OR (tRecover.Q AND bAutoRecover ) THEN
    This^.M_Recover();
    tRecover.IN := FALSE;
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;
 stPump.iAutoOnTimer:= TIME_TO_INT(tRecover.PT);
END_ACTION

METHOD M_AutoOn : BOOL
VAR_INPUT
END_VAR
this^.stPump.xAutoOn:= TRUE; // Pump is set to auto start when the pressure is low enough
END_METHOD

METHOD M_Recover : BOOL
VAR_INPUT
END_VAR
This^.M_Run(This^.bWasOn);
M_Recover := bWasOn;
IF (bWasOn) THEN fbLogger(sMsg:='Auto Recovery Initiated.', eSevr:=TcEventSeverity.Info); END_IF;
END_METHOD

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.stPump.xHVEna_SW := run;
END_METHOD
Related:

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

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

FB_PIP_SIP

(* This function block does basic controls FOR the ION pump connected to a SAES SIP controller.
 Provides interlocking interface. Enable HV only when interlock gauge press is less than 1.0E-4 Torr *)
{attribute 'reflection'}
FUNCTION_BLOCK FB_PIP_SIP 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
 rPRESS : REAL;
 iTermBits: UINT := 32767 ; // The terminal's maximum value in bits
 rV : REAL;
 timer:TON;

 (* IO Controls *)
 q_xHVEna_DO AT %Q*: BOOL;  // Enable High Voltage when TRUE
 i_iPRESS  AT %I*: INT;  //
 i_xIP_ON  AT %I* : BOOL;
 i_xError  AT %I* : BOOL;

// 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
VAR CONSTANT
     rDefaultHVEna_SP : REAL :=1E-4; // Default protection setpoint as per the gamma QPCe manual
END_VAR
VAR PERSISTENT
    rHVEna_SP : REAL;
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 > rDefaultHVEna_SP) THEN
    stPump.rHVEna_SP := rDefaultHVEna_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 OR i_xError  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();
(*Load or save the persistent variables*)
ACT_Persistent();

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_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rHVEna_SP <> 0) THEN
            stPump.rHVEna_SP := rHVEna_SP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (stPump.rHVEna_SP = rHVEna_SP) THEN
    rHVEna_SP := stPump.rHVEna_SP;
END_IF;
END_ACTION

ACTION ACT_SetGauge:
(*MG*)
(* convert the analog input into readable pressure*)
(* use logarithmic Pressure calculation, Output= Normal and Offset = 0*)
If (iTermBits=0) THEN iTermBits := 32767;END_IF
rV := 10*INT_TO_REAL(i_iPress)/iTermBits;
//Manual Page 29
IF (rV >= 2.5) THEN
    rPRESS := LREAL_TO_REAL((0.000000001*EXP(1.8421*rV))/65);
    ELSE
    rPRESS := LREAL_TO_REAL((0.0000000008*EXP(1.9315*rV))/65);
END_IF
// Min. measured pressure
IF (rPRESS < 1E-11) AND NOT (rPRESS = 0)  THEN
    rPRESS := 1E-11;
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 := i_xError;// (stPump.eState = pumpFAULT);
stPump.xOverrideMode := i_xOverrideMode;
(*inputs*)
stPump.i_iPRESS:=   i_iPRESS;
stPump.i_xHV_DI:=   i_xIP_ON;
(*output*)
q_xHVEna_DO:=  stPump.q_xHVEna_DO; //According to Manual pin should be grounded to activate function

 This^.stPump.sPath:= sPath;
END_ACTION

METHOD M_AutoOn : BOOL
VAR_INPUT
END_VAR
this^.stPump.xAutoOn:= TRUE; // Pump is set to auto start when the pressure is low enough
END_METHOD

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.stPump.xHVEna_SW := run;
END_METHOD
Related:

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

METHOD M_AutoStart
VAR_INPUT
END_VAR
TEST('PIP_Auto_Restart');
This^.PG.rPRESS := 1E-6;
This^.PG.xPRESS_OK := TRUE;
This^.fb_PIP_GAMMA(i_stGauge:=PG, stPump=>, q_IG=>);
AssertTrue(This^.fb_PIP_GAMMA.stPump.q_xHVEna_DO, 'PIP HVEna_DO set to false');
TEST_FINISHED_NAMED('PIP_Auto_Restart');
END_METHOD

METHOD M_Check_State
VAR_INPUT
END_VAR

END_METHOD

METHOD M_INIT
VAR_INPUT
END_VAR
TEST('PIP_Startup');
This^.PG.rPRESS := 1E-1;
This^.PG.xPRESS_OK := TRUE;
This^.fb_PIP_GAMMA(i_stGauge:=PG, stPump=>, q_IG=>);
AssertFalse(This^.fb_PIP_GAMMA.stPump.xHVEna_SW, 'PIP xHVEna_SW set to True');
AssertFalse(This^.fb_PIP_GAMMA.stPump.q_xHVEna_DO, 'PIP HVEna_DO set to True');
TEST_FINISHED_NAMED('PIP_Startup');
END_METHOD

METHOD M_Interlock
VAR_INPUT
END_VAR
TEST('PIP_Interlock_Active');
This^.PG.rPRESS := 1E-1;
This^.PG.xPRESS_OK := True;
This^.fb_PIP_GAMMA.M_Run(TRUE);
This^.fb_PIP_GAMMA(i_stGauge:=PG, stPump=>, q_IG=>);
AssertFalse(This^.fb_PIP_GAMMA.stPump.xHVEna_SW, 'PIP xHVEna_SW set to True');
AssertFalse(This^.fb_PIP_GAMMA.stPump.q_xHVEna_DO, 'PIP HVEna_DO set to True');
TEST_FINISHED_NAMED('PIP_Interlock_Active');

TEST('PIP_Interlock_OK');
This^.PG.rPRESS := 1E-9;
This^.PG.xPRESS_OK := True;
This^.fb_PIP_GAMMA.M_Run(TRUE);
This^.fb_PIP_GAMMA(i_stGauge:=PG, stPump=>, q_IG=>);
AssertTrue(This^.fb_PIP_GAMMA.stPump.xHVEna_SW, 'PIP xHVEna_SW set to False');
AssertTrue(This^.fb_PIP_GAMMA.stPump.q_xHVEna_DO, 'PIP HVEna_DO set to False');
TEST_FINISHED_NAMED('PIP_Interlock_OK');

TEST('PIP_Interlock_Lost');
This^.PG.rPRESS := 1E-9;
This^.PG.xPRESS_OK := False;
This^.fb_PIP_GAMMA(i_stGauge:=PG, stPump=>, q_IG=>);
AssertFalse(This^.fb_PIP_GAMMA.stPump.xHVEna_SW, 'PIP xHVEna_SW set to True');
AssertFalse(This^.fb_PIP_GAMMA.stPump.q_xHVEna_DO, 'PIP HVEna_DO set to True');
TEST_FINISHED_NAMED('PIP_Interlock_Lost');
END_METHOD
Related:

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

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();
(*Load or save the persistent variables*)
ACT_Persistent();

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 ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rBackingPressureSP <> 0) THEN
            iq_stPTM.rBackingPressureSP := rBackingPressureSP;
    END_IF;
    IF (rInletPressureSP <> 0) THEN
            iq_stPTM.rInletPressureSP := rInletPressureSP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stPTM.rBackingPressureSP = rBackingPressureSP) THEN
    rBackingPressureSP:= iq_stPTM.rBackingPressureSP;
END_IF;

IF NOT (iq_stPTM.rInletPressureSP = rInletPressureSP) THEN
    rInletPressureSP := iq_stPTM.rInletPressureSP;
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

METHOD BackingPressureSetPoint : REAL
VAR_INPUT
    i_SetPoint : REAL;
    rMaxBackingPressure: REAL;
END_VAR
BackingPressureSetPoint := Min(i_SetPoint , rMaxBackingPressure);
END_METHOD

METHOD PUBLIC ErrorMessage : STRING
VAR_INPUT
    nErrorCode : INT;
    eState: E_PumpState;
END_VAR
IF (eState = pumpFAULT ) THEN
    CASE nErrorCode OF
            950:
                    ErrorMessage := 'Pump Starting Fault';
            930:
                    ErrorMessage := 'Pump could not achieve running speed';

            920:
                    ErrorMessage := 'Running at speed signal lost multiple times';

            900:
                    ErrorMessage := 'Pump Controller Fault';
    END_CASE

END_IF
END_METHOD

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.iq_stPtm.xRunSW := run;
END_METHOD

METHOD M_SetBits : BOOL
VAR_INPUT
    TermBits : UINT; // The terminal's maximum value in bits
END_VAR
This^.iTermBits := TermBits;
END_METHOD

METHOD M_SetCurrentScale : BOOL
VAR_INPUT
    Scale:REAL;//In Amps
END_VAR
This^.rCurrentScale := Scale;
END_METHOD

METHOD M_SetMaxSpeed : BOOL
VAR_INPUT
    iMaxSpeed: INT; //963Hz for 3.1,300HT and 1010Hz for 401 and 1350 for 81
END_VAR
this^.iMaxSpeed := iMaxSpeed;
END_METHOD

METHOD M_SetPowerScale : BOOL
VAR_INPUT
    Scale:REAL;//In Watt
END_VAR
This^.rPowerScale := Scale;
END_METHOD

METHOD PUBLIC M_TurboIsOn : BOOL
VAR_INPUT
END_VAR
M_TurboIsOn := (This^.iq_stPtm.eState =  E_PumpState.pumpSTARTING ) OR (This^.iq_stPtm.eState =  E_PumpState.pumpRUNNING );
END_METHOD
Related:

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();
(*Load or save the persistent variables*)
ACT_Persistent();

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

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rBackingPressureSP <> 0) THEN
            iq_stPTM.rBackingPressureSP := rBackingPressureSP;
    END_IF;
    IF (rInletPressureSP <> 0) THEN
            iq_stPTM.rInletPressureSP := rInletPressureSP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stPTM.rBackingPressureSP = rBackingPressureSP) THEN
    rBackingPressureSP:= iq_stPTM.rBackingPressureSP;
END_IF;

IF NOT (iq_stPTM.rInletPressureSP = rInletPressureSP) THEN
    rInletPressureSP := iq_stPTM.rInletPressureSP;
END_IF;
END_ACTION

METHOD PUBLIC BackingPressureSetPoint : REAL
VAR_INPUT
    i_SetPoint : REAL;
    rMaxBackingPressure: REAL;
END_VAR
BackingPressureSetPoint := Min(i_SetPoint , rMaxBackingPressure);
END_METHOD

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.iq_stPtm.xRunSW := run;
END_METHOD
Related:

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();
(*Load or save the persistent variables*)
ACT_Persistent();

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 *)
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 )
(*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

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rBackingPressureSP <> 0) THEN
            iq_stPTM.rBackingPressureSP := rBackingPressureSP;
    END_IF;
    IF (rInletPressureSP <> 0) THEN
            iq_stPTM.rInletPressureSP := rInletPressureSP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stPTM.rBackingPressureSP = rBackingPressureSP) THEN
    rBackingPressureSP:= iq_stPTM.rBackingPressureSP;
END_IF;

IF NOT (iq_stPTM.rInletPressureSP = rInletPressureSP) THEN
    rInletPressureSP := iq_stPTM.rInletPressureSP;
END_IF;
END_ACTION

METHOD PUBLIC BackingPressureSetPoint : REAL
VAR_INPUT
    i_SetPoint : REAL;
    rMaxBackingPressure: REAL;
END_VAR
BackingPressureSetPoint := Min(i_SetPoint , rMaxBackingPressure);
END_METHOD

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.iq_stPtm.xRunSW := run;
END_METHOD
Related:

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();
(*Load or save the persistent variables*)
ACT_Persistent();

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

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rBackingPressureSP <> 0) THEN
            iq_stPTM.rBackingPressureSP := rBackingPressureSP;
    END_IF;
    IF (rInletPressureSP <> 0) THEN
            iq_stPTM.rInletPressureSP := rInletPressureSP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stPTM.rBackingPressureSP = rBackingPressureSP) THEN
    rBackingPressureSP:= iq_stPTM.rBackingPressureSP;
END_IF;

IF NOT (iq_stPTM.rInletPressureSP = rInletPressureSP) THEN
    rInletPressureSP := iq_stPTM.rInletPressureSP;
END_IF;
END_ACTION

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.iq_stPtm.xRunSW := run;
END_METHOD
Related:

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_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;
    q_PumpingStation := TRUE;
 END_IF

 IF (iq_stPTM.xResetSW) AND (iq_stPTM.i_xFault)  THEN
    q_PumpingStation := 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();
(*Load or save the persistent variables*)
ACT_Persistent();

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

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rBackingPressureSP <> 0) THEN
            iq_stPTM.rBackingPressureSP := rBackingPressureSP;
    END_IF;
    IF (rInletPressureSP <> 0) THEN
            iq_stPTM.rInletPressureSP := rInletPressureSP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stPTM.rBackingPressureSP = rBackingPressureSP) THEN
    rBackingPressureSP:= iq_stPTM.rBackingPressureSP;
END_IF;

IF NOT (iq_stPTM.rInletPressureSP = rInletPressureSP) THEN
    rInletPressureSP := iq_stPTM.rInletPressureSP;
END_IF;
END_ACTION

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.iq_stPtm.xRunSW := run;
END_METHOD

METHOD PUBLIC M_Serial_IO : BOOL
VAR_INPUT
    st_Pfeiffer_RBK: ST_PfeifferStatus;
END_VAR
VAR_IN_OUT
    st_Pfeiffer_CTRL: ST_PfeifferControl;
END_VAR
(* This function block is used to map the IO points from the pfeiffer structures
to the generic turbo structures *)
(*
if (bInit) Then
    bInit := False;
    st_Pfeiffer_CTRL.diSetRotationSpeed_309 := st_Pfeiffer_RBK.diActiveRotationSpeed_309;
END_IF*)

(* Controls *) (* Controls is via DIO*)
//st_Pfeiffer_CTRL.xPumpingStation_010 := iq_stPTM.q_RunDO;
//st_Pfeiffer_CTRL.xMotorPump_023 := 1; //Setting always true because parameter 10 alone can enable/disable the pump
//st_Pfeiffer_CTRL.uiPowerPct_708 := iq_stPTM.q_uiPowerPct;
//st_Pfeiffer_CTRL.xErrorAck_009 := iq_stPTM.xResetSW;



(* Readbacks *)
iq_stPTM.i_xAccel := st_Pfeiffer_RBK.xPumpAccelerating_307;
//iq_stPTM.i_xAtSpd := st_Pfeiffer_RBK.xRotationSpeedAttn_306; The Atspd is already linked via Digital I/Os
iq_stPTM.i_diCurSpd := st_Pfeiffer_RBK.diActiveRotationSpeed_309;
iq_stPTM.i_diPwr := st_Pfeiffer_RBK.diDrivePower_316;
iq_stPTM.i_diElecTemp := st_Pfeiffer_RBK.diTempElec_326;
iq_stPTM.i_diBtmTemp := st_Pfeiffer_RBK.diTempPmpBot_330;
iq_stPTM.i_diBrngTemp := st_Pfeiffer_RBK.diTempBearng_342;
iq_stPTM.i_diMtrTemp := st_Pfeiffer_RBK.diTempMotor_346;
iq_stPTM.i_uiPowerPctRbk := st_Pfeiffer_RBK.uiPowerPct_708;
iq_stPTM.i_rCurrentMon :=  st_Pfeiffer_RBK.lrDriveCurrent_310;
iq_stPTM.xCommTimeout       :=      st_Pfeiffer_RBK.xTimeout;
iq_stPTM.i_iErrorCode := st_Pfeiffer_RBK.iErrorCode_303;
iq_stPTM.i_iWarningCode := st_Pfeiffer_RBK.iWarningCode_303;

(* Check that error acknowledgment is processed *)
IF st_Pfeiffer_RBK.xErrorAcknowledged THEN
    st_Pfeiffer_RBK.xErrorAcknowledged := FALSE;
    st_Pfeiffer_CTRL.xErrorAck_009 := FALSE;
    iq_stPTM.xResetSW := FALSE;
END_IF

IF st_Pfeiffer_RBK.sErrorCode_303 = '' THEN
    ;
ELSIF st_Pfeiffer_RBK.sErrorCode_303 <> '000000' THEN
iq_stPTM.i_xFault := TRUE;
ELSE
iq_stPTM.i_xFault := FALSE;
END_IF

st_Pfeiffer_CTRL.xSetSpdMode_026:= iq_stPTM.q_bSetSpd;
iq_stPTM.i_dSetSpd := LIMIT (0, iq_stPTM.i_dSetSpd, 1500); // 1500 nominal frequency
IF ( iq_stPTM.i_dSetSpd <>0) THEN
    st_Pfeiffer_CTRL.iSetRotationSpeed_707 := DINT_TO_INT(iq_stPTM.i_dSetSpd * 100/1500);
    st_Pfeiffer_CTRL.iSetRotationSpeed_707 := LIMIT(20, st_Pfeiffer_CTRL.iSetRotationSpeed_707 ,100); // Manual Page 18
END_IF
END_METHOD
Related:

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

METHOD M_INIT : BOOL
VAR_INPUT
END_VAR
TEST('PTM_Twistorr_Startup');
fb_TwisTorr(i_xExtILKOk:= TRUE , iq_stPtm=> );
IF( cycle = 3) THEN
    AssertFalse(fb_TwisTorr.iq_stPtm.xRunSW ,'PTM Twistorr xRunSW is set to true');
    AssertFalse(fb_TwisTorr.iq_stPtm.q_RunDO ,'PTM Twistorr q_RunDO is set to true');
    TEST_FINISHED_NAMED('PTM_Twistorr_Startup');
END_IF


TEST('PTM_Ebara011M_Startup');
fb_PTM_Ebara_010M(i_xExtILKOk:= TRUE , iq_stPtm=> );
IF( cycle = 3) THEN
    AssertFalse(fb_PTM_Ebara_010M.iq_stPtm.xRunSW ,'PTM Ebara011M xRunSW is set to true');
    AssertFalse(fb_PTM_Ebara_010M.iq_stPtm.q_RunDO ,'PTM Ebara011M q_RunDO is set to true');
    AssertFalse(fb_PTM_Ebara_010M.iq_stPtm.q_xProtection ,'PTM Ebara011M q_xProtection is set to True');
    TEST_FINISHED_NAMED('PTM_Ebara011M_Startup');
END_IF
END_METHOD

METHOD M_Interlock
VAR_INPUT
END_VAR
TEST('PTM_Twistorr_Interlock_Active');
fb_TwisTorr.M_Run(TRUE);
fb_TwisTorr(i_xExtILKOk:= FALSE , iq_stPtm=> );
IF( cycle = 3) THEN
    AssertFalse(fb_TwisTorr.iq_stPtm.xRunSW ,'PTM Twistorr xRunSW is set to true');
    AssertFalse(fb_TwisTorr.iq_stPtm.q_RunDO ,'PTM Twistorr q_RunDO is set to true');
    TEST_FINISHED_NAMED('PTM_Twistorr_Interlock_Active');
END_IF

TEST('PTM_Twistorr_Interlock_OK');
fb_TwisTorr.M_Run(TRUE);
fb_TwisTorr(i_xExtILKOk:= TRUE , iq_stPtm=> );
IF( cycle = 3) THEN
    AssertTrue(fb_TwisTorr.iq_stPtm.xRunSW ,'PTM Twistorr xRunSW is set to False');
    AssertTrue(fb_TwisTorr.iq_stPtm.q_RunDO ,'PTM Twistorr q_RunDO is set to False');
    TEST_FINISHED_NAMED('PTM_Twistorr_Interlock_OK');
END_IF

TEST('PTM_Twistorr_Interlock_Lost');
fb_TwisTorr(i_xExtILKOk:= FALSE , iq_stPtm=> );
IF( cycle = 3) THEN
    AssertFalse(fb_TwisTorr.iq_stPtm.xRunSW ,'PTM Twistorr xRunSW is set to true');
    AssertFalse(fb_TwisTorr.iq_stPtm.q_RunDO ,'PTM Twistorr q_RunDO is set to true');
    TEST_FINISHED_NAMED('PTM_Twistorr_Interlock_Lost');
END_IF
END_METHOD

METHOD M_PTM_EBARA
VAR
    Expected:REAL;
    Actual:REAL;
    diExpected:DINT;
    diActual: DINT;
END_VAR
TEST('PTM_EBARA_AI');
This^.i_iRawSpeed := v_to_int(5);
This^.i_iTempMon := v_to_int(2.5);
THIS^.i_iCurrentMon := v_to_int(2.5);
//THIS^.q_iSpeedSet := 560;

fb_PTM_Ebara_010M(
    i_xExtILKOk:= TRUE ,
    i_rMaxBackingPressure:= 1,
    iq_stPTM=> );


IF( cycle = 10) THEN
    Expected := 5;
    Actual := (fb_PTM_Ebara_010M.iq_stPTM.i_rCurrentMon);
    AssertEquals_REAL(Expected, Actual,0.1, 'PTM EBARA010 i_iCurrentMon Fail');
    Expected := 50;
    Actual := (fb_PTM_Ebara_010M.iq_stPTM.i_rTempMon);
    AssertEquals_REAL(Expected, Actual,0.1, 'PTM EBARA010 i_iTempMon Fail');
    diExpected := 0;
    diActual := fb_PTM_Ebara_010M.iq_stPTM.q_iSpeedSet;
    AssertEquals(diExpected, diActual, 'PTM EBARA010 q_iSpeedSet Fail');
    diExpected := 560;
    diActual :=  fb_PTM_Ebara_010M.iq_stPTM.i_diCurSpd;
    AssertEquals(diExpected, diActual, 'PTM EBARA010 i_diCurSpd Fail');
    TEST_FINISHED_NAMED('PTM_EBARA_AI');


End_IF;
END_METHOD
Related:

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_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
    q_RunDO := iq_stPTM.xRunSW;
ELSE
    iq_stPtm.xRunSW:=FALSE;
    q_RunDO:=FALSE;
END_IF

(*Pump States*)
IF (iq_stPtm.i_xFault) THEN
            iq_stPtm.eState := pumpFAULT;
            iq_stPtm.xRunSW:=FALSE;
            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();
(*Load or save the persistent variables*)
ACT_Persistent();

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*)
iq_stPtm.q_RunDO := 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

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rBackingPressureSP <> 0) THEN
            iq_stPTM.rBackingPressureSP := rBackingPressureSP;
    END_IF;
    IF (rInletPressureSP <> 0) THEN
            iq_stPTM.rInletPressureSP := rInletPressureSP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stPTM.rBackingPressureSP = rBackingPressureSP) THEN
    rBackingPressureSP:= iq_stPTM.rBackingPressureSP;
END_IF;

IF NOT (iq_stPTM.rInletPressureSP = rInletPressureSP) THEN
    rInletPressureSP := iq_stPTM.rInletPressureSP;
END_IF;
END_ACTION

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.iq_stPtm.xRunSW := run;
END_METHOD

METHOD PUBLIC M_Serial_IO : BOOL
VAR_INPUT
    i_st_TD20_RBK                   : ST_TD20Status;
END_VAR
(* This function block is used to map the IO points from the Oerlikon structures
to the generic turbo structures *)

(* Readbacks *)
iq_stPTM.i_xAccel := i_st_TD20_RBK.wStatusWord.4;
iq_stPTM.i_xDecel := i_st_TD20_RBK.wStatusWord.5;
//iq_stPTM.i_xAtSpd := i_st_TD20_RBK.wStatusWord.10;
iq_stPTM.i_xRemote := i_st_TD20_RBK.wStatusWord.15; (* Remote control enabled *)
iq_stPTM.i_diCurSpd := WORD_TO_DINT(i_st_TD20_RBK.wFrequency_P3);
iq_stPTM.i_rCurrentMon := WORD_TO_INT(i_st_TD20_RBK.wMotorCurrent_P5);
iq_stPTM.i_diPwr := WORD_TO_DINT(i_st_TD20_RBK.wMotorCurrent_P5 * i_st_TD20_RBK.wVoltage_P4);
iq_stPTM.i_diElecTemp := WORD_TO_DINT(i_st_TD20_RBK.wConverterTemp_P11);
iq_stPTM.i_rTempMon := WORD_TO_INT(i_st_TD20_RBK.wPumpTemp_P127);

(* Faults and warnings *)
//iq_stPTM.i_xFault := i_st_TD20_RBK.wStatusWord.3;
iq_stPTM.i_xTempFault := i_st_TD20_RBK.wStatusWord.7; (* specified as a temperature warning but it could be a fault *)
iq_stPTM.i_xWarn := i_st_TD20_RBK.wStatusWord.14; (* warning summary bit *)
END_METHOD
Related:

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();
(*Load or save the persistent variables*)
ACT_Persistent();

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

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rBackingPressureSP <> 0) THEN
            iq_stPTM.rBackingPressureSP := rBackingPressureSP;
    END_IF;
    IF (rInletPressureSP <> 0) THEN
            iq_stPTM.rInletPressureSP := rInletPressureSP;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stPTM.rBackingPressureSP = rBackingPressureSP) THEN
    rBackingPressureSP:= iq_stPTM.rBackingPressureSP;
END_IF;

IF NOT (iq_stPTM.rInletPressureSP = rInletPressureSP) THEN
    rInletPressureSP := iq_stPTM.rInletPressureSP;
END_IF;
END_ACTION

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.iq_stPtm.xRunSW := run;
END_METHOD
Related:

FB_Pump

FUNCTION_BLOCK FB_Pump
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;
    // For Persistent Data
    bRestorePersistentData  :       BOOL:=TRUE;
END_VAR
VAR PERSISTENT
    rBackingPressureSP      :       REAL;
    rInletPressureSP        :       REAL;
END_VAR


END_FUNCTION_BLOCK

ACTION ACT_Logger:

END_ACTION
Related:

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

METHOD PUBLIC M_Run : BOOL
VAR_INPUT
    run:bool; // set to true to run, false to stop;
END_VAR
this^.iq_stPFO.pv_xRunSW := run;
END_METHOD
Related:

FB_TBOOL_ADS

(* This function block is created for interface a boolean singal between different PLCs*)
(* The variable values are read via ADS using the symbol name*)
FUNCTION_BLOCK FB_TBOOL_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: value'}
    bBool : BOOL;
    {attribute 'pytmc' := 'pv: error'}
    bError: BOOL;
END_VAR
VAR
    fb_CheckWatchdog: FB_CheckWatchdog;
    fb_Read: FB_ReadAdsSymByName;
    ftReset: F_TRIG;
    xFirstPass: BOOL:= true;

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

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


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

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

IF (fb_Read.bError OR fb_CheckWatchdog.bWatchdog) THEN
    bBool := FALSE; // If there is an error in connection the signal turns to FALSE
END_IF;

(*Logger*)
ACT_Logger();

END_FUNCTION_BLOCK

ACTION ACT_Logger:
IF tErrorPresent.Q THEN
 IF(fb_Read.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
Related:

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

FB_TGPI_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_TGPI_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,'.PG'),
    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
Related:

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

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

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

METHOD M_Close : BOOL
VAR_INPUT
            close:BOOL; //True to CLose, False to OPEN
END_VAR
This^.q_stValve.pv_xCLS_SW :=Close;
END_METHOD
Related:

FB_TVGC_2S_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_2S_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_2S;
    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
Related:

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

FB_Valve

FUNCTION_BLOCK FB_Valve
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    // For logging
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.VACUUM, nMinTimeViolationAcceptable:=10);
    ePrevState : E_ValvePositionState;
    tErrorPresent : R_TRIG;
    tAction : R_TRIG; // Primary action of this device (OPN_DO, etc.)
    tOverrideActivated : R_TRIG;
    // For Persistent Data
    bRestorePersistentData  :       BOOL:=TRUE;
    //fbWritePersistentData           : WritePersistentData;
END_VAR


END_FUNCTION_BLOCK

ACTION ACT_Logger:

END_ACTION
Related:

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

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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := NOT This^.q_xOPN_DO;
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := This^.q_xOPN_DO;
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
            value:BOOL;  //True to SET, False to RESET
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

FB_VCC_NO

(*Deprecated*)
(* This Function Block Implements Basic Functionality for NO Vent Valves VVC *)
(* Note Interlock Logic is External *)
{attribute 'obsolete' := 'FB_VCC_NO has been deprecated and is not valid!'}
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

METHOD M_Close : BOOL
VAR_INPUT
            close:BOOL; //True to CLose, False to OPEN
END_VAR
This^.iq_stValve.pv_xCLS_SW :=Close;
END_METHOD
Related:

FB_VCN

(* This function implements the Basic functions for the Pfeiffer EVR 116 needle valve*)
FUNCTION_BLOCK FB_VCN EXTENDS FB_Valve
VAR_INPUT
    i_xExtIlkOK     :       BOOL; //External Interlock, SET to TRUE if not used
    i_ReqPos        :       REAL; //Requested position
    i_xPurge        : BOOL:= FALSE;// When purge is set the maximum threshold is ignored, has to be set evey cycle.
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    iq_stVCN        :       ST_VCN; //Needle valve structure
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
VAR PERSISTENT
    rUpperLimit     :       REAL;
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
*)


iq_stVCN.rUpperLimit :=  LIMIT(0, iq_stVCN.rUpperLimit, 100);

// 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*)
    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
            IF (i_xPurge) THEN
            iq_stVCN.rReqPosition := LIMIT(0, i_ReqPos, 100);
            ELSE iq_stVCN.rReqPosition := LIMIT(0, i_ReqPos, iq_stVCN.rUpperLimit);
            END_IF
    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();

(*Load or save the persistent variables*)
ACT_Persistent();

END_FUNCTION_BLOCK

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

ACTION ACT_Logger:
// ILK logger

IF NOT (iq_stVCN.xIlkOK ) AND NOT(ePrevState = CloseValve) THEN
            fbLogger(sMsg:='Lost interlock ok bit while valve was open.', eSevr:=TcEventSeverity.Critical);
END_IF


//Control STATE Logger
IF ePrevState <> iq_stVCN.eValveControl THEN
      CASE iq_stVCN.eValveControl OF
            ManualControl:
                    fbLogger(sMsg:='Valve in manual control mode', eSevr:=TcEventSeverity.Warning);
            OpenValve:
                    fbLogger(sMsg:='Valve Open.', eSevr:=TcEventSeverity.Info);
            PressureControl:
                    fbLogger(sMsg:='Valve in pressure control mode.', eSevr:=TcEventSeverity.Info);
            CloseValve:
                    fbLogger(sMsg:='Valve closed.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePrevState := iq_stVCN.eValveControl;
  END_IF


// Log valve open
tAction(CLK:= (iq_stVCN.eValveControl = OpenValve) );
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded to fully open', eSevr:=TcEventSeverity.Info); END_IF
END_ACTION

ACTION ACT_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (bRestorePersistentData) THEN
    bRestorePersistentData  := FALSE;
    IF (rUpperLimit <> 0) THEN
            iq_stVCN.rUpperLimit := rUpperLimit;
    END_IF;

END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stVCN.rUpperLimit = rUpperLimit) THEN
    rUpperLimit := iq_stVCN.rUpperLimit;
    //fbWritePersistentData(NETID:='', PORT:=851, START:=TRUE, TMOUT:=T#1s );
ELSE
     // fbWritePersistentData( START:=FALSE);
END_IF;
END_ACTION

METHOD M_SetThrottle : BOOL
VAR_INPUT
    rUpperLimit     :       REAL;
END_VAR
iq_stVCN.rUpperLimit := LIMIT(0, rUpperLimit, 100);
END_METHOD

METHOD M_ValveControl : BOOL
VAR_INPUT
    state:E_VCN; // Close, Open, Pressure, Manual
END_VAR
iq_stVCN.eValveControl := state;
END_METHOD
Related:

FB_VCN_VAT590

(* This function implements the Basic functions for the VAT590 needle valve*)
FUNCTION_BLOCK FB_VCN_VAT590 EXTENDS FB_Valve
VAR_INPUT
    bExtIlkOK : BOOL;        // External Interlock, SET to TRUE if not used
    IG : ST_VG;              // Input from a cold cathode gauge used in control loop
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
    pv:
    '}
    stVcnVat590     : ST_VCN_VAT590;       // Needle valve VAT590 structure
END_VAR
VAR CONSTANT
    cOpenPosition : REAL := 1E+5;      // Default position setpoint upper scale value
    cClosePosition : REAL := 0.0;      // Default position setpoint lower scale value
    cPresSPUpLim : REAL := 1E+6;       // Default pressure setpoint upper scale value
    cPresSPLowLim : REAL := 0;         // Default pressure setpoint lower scale value
    cMinPress : REAL := 1e-11;         // Default Min. pressure in Torr that can be used and requested
    cMaxPress : REAL := 1e-2;          // Default Max. pressure in Torr that can be used and requested
    cBasePress : REAL := 1E-11;        // Pressure sensor base value
    cPressRawSlope : REAL := 1E+5;     // Slope (in Units per decade) used for conversion of pressure in Torr to valve External Digital pressure sensor 1 input raw value (0-1000000)
    cPressRawBase : REAL := 1E+5;      // Valve analog output base(in Units per decade) defined for how much the curve is shifted

    cPressCtrl : ST_CoEIndSub := (nIndex := 16#2199, nSubIndex := 0); // Selected pressure controller CoE register
    cSensorDelay_AD : ST_CoEIndSub := (nIndex := 16#219A, nSubIndex := 0); // Adaptive DS controller Sensor Delay CoE register
    cRampTime_AD : ST_CoEIndSub := (nIndex := 16#219B, nSubIndex := 0); // Adaptive DS controller Ramp Time CoE register
    cRampMode_AD : ST_CoEIndSub := (nIndex := 16#219C, nSubIndex := 0); // Adaptive DS controller Ramp Mode CoE register
    cGainFactor_AD : ST_CoEIndSub := (nIndex := 16#219E, nSubIndex := 0); // Adaptive DS controller Gain factor CoE register
    cRampTime_FIX1 : ST_CoEIndSub := (nIndex := 16#21A5, nSubIndex := 0); // Fixed 1 controller Ramp Time CoE register
    cRampMode_FIX1 : ST_CoEIndSub := (nIndex := 16#21A6, nSubIndex := 0); // Fixed 1 controller Ramp Mode CoE register
    cCtrlDir_FIX1 : ST_CoEIndSub := (nIndex := 16#21A7, nSubIndex := 0); // Fixed 1 controller Control Direction CoE register
    cPGain_FIX1 : ST_CoEIndSub := (nIndex := 16#21A8, nSubIndex := 0); // Fixed 1 controller P Gain CoE register
    cIGain_FIX1 : ST_CoEIndSub := (nIndex := 16#21A9, nSubIndex := 0); // Fixed 1 controller I Gain CoE register
    cRampTime_FIX2 : ST_CoEIndSub := (nIndex := 16#21AF, nSubIndex := 0); // Fixed 2 controller Ramp Time CoE register
    cRampMode_FIX2 : ST_CoEIndSub := (nIndex := 16#21B0, nSubIndex := 0); // Fixed 2 controller Ramp Mode CoE register
    cCtrlDir_FIX2 : ST_CoEIndSub := (nIndex := 16#21B1, nSubIndex := 0); // Fixed 2 controller Control Direction CoE register
    cPGain_FIX2 : ST_CoEIndSub := (nIndex := 16#21B2, nSubIndex := 0); // Fixed 2 controller P Gain CoE register
    cIGain_FIX2 : ST_CoEIndSub := (nIndex := 16#21B3, nSubIndex := 0); // Fixed 2 controller I Gain CoE register
    cRampTime_SP : ST_CoEIndSub := (nIndex := 16#21B9, nSubIndex := 0); // Soft Pump controller Ramp Time CoE register
    cRampMode_SP : ST_CoEIndSub := (nIndex := 16#21BA, nSubIndex := 0); // Soft Pump controller Ramp Time CoE register
    cPGain_SP : ST_CoEIndSub := (nIndex := 16#21BC, nSubIndex := 0); // Soft Pump controller Ramp Time CoE register
    cPosLimitOpen : ST_CoEIndSub := (nIndex := 16#56B3, nSubIndex := 0); // General open limit. It overides the PosLimitOpenCtrl if SET lower
    cPosLimitOpenCtrl : ST_CoEIndSub := (nIndex := 16#56B4, nSubIndex := 0); // Open limit active during pressure control only
END_VAR
VAR
    fCalcPosSP : REAL := 0;
    fCalcPresSP : REAL := 0;
    fPressRaw : REAL := 0;             // Ranges 0 to 1000000 (or 0.0 to 10V) (1E-12 to 1E-2)
    eValveModeSP : E_ControlModeSP;    // Control Mode setpoint internal variable
    fReqPos : REAL;                    // Requested position internal variable
    fReqPres : REAL;                   // Requested pressure internal variable

    bRemAcsToggle : BOOL;              // Remote access control bit
    //timer for zero function
    tZeroTogg : TON := (PT := T#1S);
    //timer for remote function
    tRemAcsTogg : TON := (PT := T#500MS);
    //used for logging
    ePreviousState : E_VCN_VAT590;

    (*CoE Interface*)
    (*Link this to your VAT590 reference variables under InfoData.*)
    //Connect i_stVat590PlcDriveRef.aNetId to the VAT590 Valve InfoData^AdsAddr^netId
    //Connect i_stVat590PlcDriveRef.nSlaveAddr to the VAT590 Valve InfoData^AdsAddr^port
    i_stVat590PlcDriveRef AT %I*: ST_PlcDriveRef;
    stVat590DriveRef : ST_DriveRef;

    fbCoeRead : FB_CoERead_ByDriveRef;   // CoE Read function block
    fbCoeWrite : FB_CoEWrite_ByDriveRef; // CoE Write function block

    stCoeParamsInternal : ST_VAT590_COE;   // Internal storage of CoE parameters
    nCaseReadCoe : UINT := 1;          // Iterator for going through all CoE parameters
    rtCoeReset : R_TRIG;               // Rising edge trigger for reset CoE Interface error
    ftCoeReadBusy : F_TRIG;            // Falling edge trigger for CoE read new data
    ftCoeWriteBusy : F_TRIG;           // Falling edge trigger for CoE write data
    bFirstRead : BOOL := TRUE;         // FALSE after succesfull first readout of all CoE parameters

    (*VAT590 Ethercat IO Interface*)
    // Ethercat Interface Inputs
    i_nRawPosition       AT %I* : DINT;               // Position readback from the valve
    i_nRawPressure       AT %I* : DINT;               // Pressure readback from the valve
    i_eControlMode       AT %I* : E_ControlMode;      // Valve control mode readback
    i_eFatalError        AT %I* : E_FatalErrors;      // Valve Fatal Errors readback
    i_stGenStatus        AT %I* : ST_GeneralStatus;   // Valve General Status readback
    i_stGenWarnings      AT %I* : ST_GeneralWarnings; // Valve General Warnings readback
    i_stExtnWarnings     AT %I* : ST_ExtendedWarnings;// Valve Extended Warnings readback
    // Ethercat Interface Outputs
    o_nRawPositionSP     AT %Q* : DINT;               // Position setpoint
    o_nRawPressureSP     AT %Q* : DINT;               // Pressure setpoint
    o_nRawPressure       AT %Q* : DINT;               // Raw pressure output, eliminate need for external connection of AI, ranges 0 to 1000000 (or 0.0 to 10V) (1E-12 Torr to 1E-2 Torr)
    o_eCtrlModeSP        AT %Q* : E_ControlModeSP;    // Valve control mode setpoint
    o_stGenCtrlSP        AT %Q* : ST_GeneralControlSP;// General Control setpoint
    (*Ethercat Interface Status Check*)
    (*Link this to your VAT590 WcState variable under WcState*)
    i_bValveEthCatStatus AT %I* : BOOL := TRUE;
END_VAR
VAR PERSISTENT
    fUpperLimit     : REAL;                // Max position in % that can be requested
    fPresMaxLim : REAL;                // Max pressure in Torr that can be requested
    fPresRbMaxLim : REAL;              // Max pressure in Torr read by chamber gauge, valve will close above the pressure.
END_VAR
(* Needle valve control FB
J. Govednik 2021-11-22

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

It provides:
Valve position ceiling
Interlock
CoE Interface R/W

It could be used for:
Valve position/flow linearization
*)

(*Set Coe Interface, AmsNetID, Slave address etc*)
SetCoeInterface();

(*Read from EPICS and Valve Ethercat interface*)
ReadFromInput();

(*Checking which Control mode is selected and if valve's communication is OK*)
IF stVcnVat590.bValveEthCatStatus THEN //If valve is not connected or something is wrong with EtherCAT communication PLC will set this bit to 1
    fReqPos := 0; //set req. position to 0
    eValveModeSP := E_ControlModeSP.CloseValve; //set control mode of the valve to CLOSE VALVE
    stVcnVat590.eValveControl := E_VCN_VAT590.CloseValve; //EPICS command hold in CloseValve state
    stVcnVat590.stValveStatus.eControlMode := E_ControlMode.FatalError; //override the readback from the valve because when valve is disconnected the value remains
    stVcnVat590.nRawPosition := 0; //override the readback from the valve because when valve is disconnected the value remains
    stVcnVat590.nRawPressure := 0; //override the readback from the valve because when valve is disconnected the value remains
ELSIF bExtIlkOK THEN
    CASE stVcnVat590.eValveControl OF
            E_VCN_VAT590.OpenValve:
                    eValveModeSP := E_ControlModeSP.OpenValve;
            E_VCN_VAT590.CloseValve:
                    eValveModeSP := E_ControlModeSP.CloseValve;
            E_VCN_VAT590.ManualControl:
                IF IG.xPRESS_OK AND IG.rPRESS <= fPresRbMaxLim THEN
                            eValveModeSP := E_ControlModeSP.ManualControl;
                    ELSE
                            eValveModeSP := E_ControlModeSP.CloseValve; // Close valve if something is wrong with the gauge
                            stVcnVat590.eValveControl := E_VCN_VAT590.CloseValve;
                    END_IF
            E_VCN_VAT590.PressureControl:
                    IF IG.xPRESS_OK AND IG.rPRESS <= fPresRbMaxLim THEN
                            eValveModeSP := E_ControlModeSP.PressureControl;
                    ELSE
                            eValveModeSP := E_ControlModeSP.CloseValve; // Close valve if something is wrong with the gauge
                            stVcnVat590.eValveControl := E_VCN_VAT590.CloseValve;
                    END_IF
            E_VCN_VAT590.HoldValve:
                    IF stVcnVat590.stValveStatus.eControlMode = E_ControlMode.ManualControl OR
                            stVcnVat590.stValveStatus.eControlMode = E_ControlMode.PressureControl THEN
                            eValveModeSP := E_ControlModeSP.HoldValve;
                    END_IF
    END_CASE
ELSE
    fReqPos := 0;
    eValveModeSP := E_ControlModeSP.CloseValve;
    stVcnVat590.eValveControl := E_VCN_VAT590.CloseValve;
END_IF

(*Position SP calculation*)
fReqPos := LIMIT(0, fReqPos, fUpperLimit);
fCalcPosSP := fReqPos * (cOpenPosition-cClosePosition)/100 + cClosePosition;
fCalcPosSP := LIMIT(cClosePosition, fCalcPosSP, cOpenPosition); //The requested position SP should remain within this range

(*Pressure SP calculation*)
//Limit the input from EPICS between upper and lower limit defined for seleceted pressure sensor
fReqPres := LIMIT(cMinPress, fReqPres, fPresMaxLim);
//Calcuate pressure setpoint
fCalcPresSP := cPressRawSlope * LOG(fReqPres/cBasePress) + cPressRawBase;
fCalcPresSP := LIMIT(cPresSPLowLim, fCalcPresSP, cPresSPUpLim); //The requested pressure SP should remain within this range

(*Calculated Pressure SP, Pressure readback and PressureRaw output value are 0 if Pressure sensor is not working*)
//Calculate PressureRaw value linked to the Valve's External Digital pressure sensor 1 input
//Pressure information from IG input is linked to stVcnVat590.fPressure
IF IG.xPRESS_OK THEN
    stVcnVat590.fPressure := IG.rPRESS;
    fPressRaw := cPressRawSlope * LOG(IG.rPRESS/cBasePress) + cPressRawBase;
ELSE
    fCalcPresSP := 0;
    stVcnVat590.fPressure := 0;
    fPressRaw := 0;
END_IF

(*Zero function*)
IF stVcnVat590.stValveStatus.bZeroDisabled OR tZeroTogg.Q THEN
    stVcnVat590.bZero := FALSE;
END_IF
tZeroTogg.IN := stVcnVat590.bZero;
tZeroTogg();

(*Set Remote Access*)
IF stVcnVat590.stValveStatus.eRemoteStatus <> stVcnVat590.eRemoteControl THEN
    tRemAcsTogg.IN := TRUE;
    IF tRemAcsTogg.Q THEN
            bRemAcsToggle := NOT bRemAcsToggle;
            tRemAcsTogg.IN := FALSE;
    END_IF
END_IF
tRemAcsTogg();

(*Reset Coe Interface error*)
rtCoeReset(CLK := stVcnVat590.bResetCoeError);
IF rtCoeReset.Q THEN
    stVcnVat590.bResetCoeError := FALSE;
END_IF

(*Write CoE Parameters*)
WriteCoeParameters();

(*Read CoE Parameters*)
ReadCoeParameters();

(*Write to EPICS and Valve Ethercat interface*)
WriteToOutput();

(*Load or save the persistent variables*)
LoadPersistentData();

(*Logging*)
Logger();

END_FUNCTION_BLOCK

ACTION LoadPersistentData:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (bRestorePersistentData) THEN
    bRestorePersistentData  := FALSE;
    IF (fUpperLimit <> 0) THEN
            stVcnVat590.fPosSetLimit := fUpperLimit;
    END_IF;
    IF (fPresMaxLim <> 0) THEN
            stVcnVat590.fPresSetLimit := fPresMaxLim;
    END_IF;
    IF (fPresRbMaxLim <> 0) THEN
            stVcnVat590.fPresRdbkLimit := fPresRbMaxLim;
    END_IF;
END_IF
END_ACTION

ACTION Logger:
// ILK logger
IF NOT (stVcnVat590.bIlkOK ) AND NOT(ePreviousState = E_VCN_VAT590.CloseValve) THEN
            fbLogger(sMsg:='Lost interlock ok bit while valve was open.', eSevr:=TcEventSeverity.Critical);
END_IF


//Control STATE Logger
IF ePreviousState <> stVcnVat590.eValveControl THEN
      CASE stVcnVat590.eValveControl OF
            E_VCN_VAT590.ManualControl:
                    fbLogger(sMsg:='Valve in manual control mode.', eSevr:=TcEventSeverity.Warning);
            E_VCN_VAT590.HoldValve:
                    fbLogger(sMsg:='Valve in hold control mode.', eSevr:=TcEventSeverity.Warning);
            E_VCN_VAT590.OpenValve:
                    fbLogger(sMsg:='Valve Open.', eSevr:=TcEventSeverity.Info);
            E_VCN_VAT590.PressureControl:
                    fbLogger(sMsg:='Valve in pressure control mode.', eSevr:=TcEventSeverity.Info);
            E_VCN_VAT590.CloseValve:
                    fbLogger(sMsg:='Valve closed.', eSevr:=TcEventSeverity.Info);
      END_CASE
      ePreviousState := stVcnVat590.eValveControl;
  END_IF


// Log valve open
tAction(CLK:= (stVcnVat590.eValveControl = E_VCN_VAT590.OpenValve) );
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded to fully open', eSevr:=TcEventSeverity.Info); END_IF
END_ACTION

ACTION ReadCoeParameters:
CASE nCaseReadCoe OF
    0:
            stVcnVat590.bCoeError := TRUE;
            IF rtCoeReset.Q THEN
                    nCaseReadCoe := 1;
                    stVcnVat590.bCoeError := FALSE;
                    bFirstRead := TRUE; // Set bFirstRead after Coe interface reset
            END_IF
    1:
            fbCoeRead.nIndex := cPressCtrl.nIndex;
            fbCoeRead.nSubIndex := cPressCtrl.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.ePIDCtrl);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.ePIDCtrl);
            fbCoeRead.bExecute := TRUE;
    2:
            fbCoeRead.nIndex := cSensorDelay_AD.nIndex;
            fbCoeRead.nSubIndex := cSensorDelay_AD.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fSensorDelay_AD);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fSensorDelay_AD);
            fbCoeRead.bExecute := TRUE;
    3:
            fbCoeRead.nIndex := cRampTime_AD.nIndex;
            fbCoeRead.nSubIndex := cRampTime_AD.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fRampTime_AD);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fRampTime_AD);
            fbCoeRead.bExecute := TRUE;
    4:
            fbCoeRead.nIndex := cRampMode_AD.nIndex;
            fbCoeRead.nSubIndex := cRampMode_AD.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.bRampMode_AD);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bRampMode_AD);
            fbCoeRead.bExecute := TRUE;
    5:
            fbCoeRead.nIndex := cGainFactor_AD.nIndex;
            fbCoeRead.nSubIndex := cGainFactor_AD.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fGainFactor_AD);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fGainFactor_AD);
            fbCoeRead.bExecute := TRUE;
    6:
            fbCoeRead.nIndex := cRampTime_FIX1.nIndex;
            fbCoeRead.nSubIndex := cRampTime_FIX1.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fRampTime_FIX1);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fRampTime_FIX1);
            fbCoeRead.bExecute := TRUE;
    7:
            fbCoeRead.nIndex := cRampMode_FIX1.nIndex;
            fbCoeRead.nSubIndex := cRampMode_FIX1.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.bRampMode_FIX1);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bRampMode_FIX1);
            fbCoeRead.bExecute := TRUE;
    8:
            fbCoeRead.nIndex := cCtrlDir_FIX1.nIndex;
            fbCoeRead.nSubIndex := cCtrlDir_FIX1.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.bCtrlDir_FIX1);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bCtrlDir_FIX1);
            fbCoeRead.bExecute := TRUE;
    9:
            fbCoeRead.nIndex := cPGain_FIX1.nIndex;
            fbCoeRead.nSubIndex := cPGain_FIX1.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fPGain_FIX1);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPGain_FIX1);
            fbCoeRead.bExecute := TRUE;
    10:
            fbCoeRead.nIndex := cIGain_FIX1.nIndex;
            fbCoeRead.nSubIndex := cIGain_FIX1.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fIGain_FIX1);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fIGain_FIX1);
            fbCoeRead.bExecute := TRUE;
    11:
            fbCoeRead.nIndex := cRampTime_FIX2.nIndex;
            fbCoeRead.nSubIndex := cRampTime_FIX2.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fRampTime_FIX2);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fRampTime_FIX2);
            fbCoeRead.bExecute := TRUE;
    12:
            fbCoeRead.nIndex := cRampMode_FIX2.nIndex;
            fbCoeRead.nSubIndex := cRampMode_FIX2.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.bRampMode_FIX2);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bRampMode_FIX2);
            fbCoeRead.bExecute := TRUE;
    13:
            fbCoeRead.nIndex := cCtrlDir_FIX2.nIndex;
            fbCoeRead.nSubIndex := cCtrlDir_FIX2.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.bCtrlDir_FIX2);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bCtrlDir_FIX2);
            fbCoeRead.bExecute := TRUE;
    14:
            fbCoeRead.nIndex := cPGain_FIX2.nIndex;
            fbCoeRead.nSubIndex := cPGain_FIX2.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fPGain_FIX2);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPGain_FIX2);
            fbCoeRead.bExecute := TRUE;
    15:
            fbCoeRead.nIndex := cIGain_FIX2.nIndex;
            fbCoeRead.nSubIndex := cIGain_FIX2.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fIGain_FIX2);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fIGain_FIX2);
            fbCoeRead.bExecute := TRUE;
    16:
            fbCoeRead.nIndex := cRampTime_SP.nIndex;
            fbCoeRead.nSubIndex := cRampTime_SP.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fRampTime_SP);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fRampTime_SP);
            fbCoeRead.bExecute := TRUE;
    17:
            fbCoeRead.nIndex := cRampMode_SP.nIndex;
            fbCoeRead.nSubIndex := cRampMode_SP.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.bRampMode_SP);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bRampMode_SP);
            fbCoeRead.bExecute := TRUE;
    18:
            fbCoeRead.nIndex := cPGain_SP.nIndex;
            fbCoeRead.nSubIndex := cPGain_SP.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fPGain_SP);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPGain_SP);
            fbCoeRead.bExecute := TRUE;
    19:
            fbCoeRead.nIndex := cPosLimitOpen.nIndex;
            fbCoeRead.nSubIndex := cPosLimitOpen.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fPositionLimitOpen);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPositionLimitOpen);
            fbCoeRead.bExecute := TRUE;
    20:
            fbCoeRead.nIndex := cPosLimitOpenCtrl.nIndex;
            fbCoeRead.nSubIndex := cPosLimitOpenCtrl.nSubIndex;
            fbCoeRead.pDstBuf := ADR(stVcnVat590.stCoeParameters.fPositionLimitOpenControl);
            fbCoeRead.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPositionLimitOpenControl);
            fbCoeRead.bExecute := TRUE;
END_CASE

ftCoeReadBusy(CLK:=fbCoeRead.bBusy);
IF ftCoeReadBusy.Q THEN
    fbCoeRead.bExecute := FALSE;
    IF NOT fbCoeRead.bError THEN
            nCaseReadCoe := nCaseReadCoe + 1;
            IF nCaseReadCoe > 20 THEN
                    nCaseReadCoe := 1;
                    bFirstRead := FALSE; // Reset bFirstRead after successfull readout of all parameters
            END_IF
    ELSE
            nCaseReadCoe := 0; // Go to Error state
    END_IF
END_IF

fbCoeRead();
stCoeParamsInternal := stVcnVat590.stCoeParameters; //Save current values to internal structure
END_ACTION

ACTION ReadFromInput:
(*Read from EPICS*)
fUpperLimit := LIMIT(0, stVcnVat590.fPosSetLimit, 100);
fPresMaxLim := LIMIT(cMinPress, stVcnVat590.fPresSetLimit, cMaxPress);
fPresRbMaxLim := LIMIT(cMinPress, stVcnVat590.fPresRdbkLimit, cMaxPress);
fReqPos := LIMIT(0, stVcnVat590.fReqPosition, 100);
fReqPres := LIMIT(cMinPress, stVcnVat590.fReqPressure, cMaxPress);

(*Read from VAT valve*)
stVcnVat590.stValveStatus.eControlMode := i_eControlMode;
stVcnVat590.stValveStatus.eFatalErrors := i_eFatalError;
stVcnVat590.nRawPosition := i_nRawPosition;
stVcnVat590.nRawPressure := i_nRawPressure;
//Mapping General statuses
stVcnVat590.stValveStatus.bFieldBusDatavalid := i_stGenStatus.Bit0FieldBusDatavalid;
stVcnVat590.stValveStatus.bZeroExecuted := i_stGenStatus.Bit1ZeroExecuted;
stVcnVat590.stValveStatus.bPingPongRxBit := i_stGenStatus.Bit2PingPongRxBit;
stVcnVat590.stValveStatus.bPressureSimulation := i_stGenStatus.Bit3PressureSimulation;
stVcnVat590.stValveStatus.bPressureSPReached := i_stGenStatus.Bit4PressureSPReached;
stVcnVat590.stValveStatus.bWarningsActive := i_stGenStatus.Bit9WarningsActive;
//Read Remote status
stVcnVat590.stValveStatus.eRemoteStatus.0 := i_stGenStatus.Bit7AccessModeBit0;
stVcnVat590.stValveStatus.eRemoteStatus.1 := i_stGenStatus.Bit8AccessModeBit1;
//Mapping General warnings
stVcnVat590.stValveStatus.bServiceRequest := i_stGenWarnings.Bit0ServiceRequest;
stVcnVat590.stValveStatus.bPowerFailureBattery := i_stGenWarnings.Bit3PowerFailureBattery;
stVcnVat590.stValveStatus.bSensMeasUnitFault := i_stGenWarnings.Bit10SensMeasUnitFault;
//Mapping Extended warnings
stVcnVat590.stValveStatus.bRemoteControlNotPossible := i_stExtnWarnings.Bit0RemoteControlNotPossible;
stVcnVat590.stValveStatus.bControlModeSPNotAllowed := i_stExtnWarnings.Bit1ControlModeSPNotAllowed;
stVcnVat590.stValveStatus.bZeroDisabled := i_stExtnWarnings.Bit2ZeroDisabled;
stVcnVat590.stValveStatus.bPFODeactivated := i_stExtnWarnings.Bit3PFODeactivated;
stVcnVat590.stValveStatus.bPressureSPOutOfRange := i_stExtnWarnings.Bit5PressureSPOutOfRange;
stVcnVat590.stValveStatus.bPositionSPOutOfRange := i_stExtnWarnings.Bit6PositionSPOutOfRange;
stVcnVat590.stValveStatus.bCtrlSPOutOfRange := i_stExtnWarnings.Bit10CtrlSPOutOfRange;
stVcnVat590.stValveStatus.bGeneralCtrlSPOutOfRange := i_stExtnWarnings.Bit11GeneralCtrlSPOutOfRange;
stVcnVat590.stValveStatus.bProcDataSettingsNotvalid := i_stExtnWarnings.Bit12ProcDataSettingsNotvalid;
//Mapping WcState - Valve Ethercat Status
stVcnVat590.bValveEthCatStatus := i_bValveEthCatStatus;
END_ACTION

ACTION SetCoeInterface:
stVat590DriveRef.sNetId := F_CreateAmsNetId(i_stVat590PlcDriveRef.aNetId);
stVat590DriveRef.nSlaveAddr := i_stVat590PlcDriveRef.nSlaveAddr;
stVat590DriveRef.nDriveNo := i_stVat590PlcDriveRef.nDriveNo;
stVat590DriveRef.nDriveType := i_stVat590PlcDriveRef.nDriveType;
fbCoeRead.stDriveRef := stVat590DriveRef;
fbCoeWrite.stDriveRef := stVat590DriveRef;
END_ACTION

ACTION WriteCoeParameters:
// User will change one parameter at a time so solution with ELSIF should work OK
IF stVcnVat590.bCoeError THEN
    IF rtCoeReset.Q THEN
            stVcnVat590.bCoeError := FALSE;
    END_IF;
ELSIF bFirstRead THEN
    ;//Wait until first readout of all CoE parameters is completed
ELSIF stVcnVat590.stCoeParameters.ePIDCtrl <> stCoeParamsInternal.ePIDCtrl THEN
    fbCoeWrite.nIndex := cPressCtrl.nIndex;
    fbCoeWrite.nSubIndex := cPressCtrl.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.ePIDCtrl);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.ePIDCtrl);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fSensorDelay_AD <> stCoeParamsInternal.fSensorDelay_AD THEN
    fbCoeWrite.nIndex := cSensorDelay_AD.nIndex;
    fbCoeWrite.nSubIndex := cSensorDelay_AD.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fSensorDelay_AD);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fSensorDelay_AD);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fRampTime_AD <> stCoeParamsInternal.fRampTime_AD THEN
    fbCoeWrite.nIndex := cRampTime_AD.nIndex;
    fbCoeWrite.nSubIndex := cRampTime_AD.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fRampTime_AD);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fRampTime_AD);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.bRampMode_AD <> stCoeParamsInternal.bRampMode_AD THEN
    fbCoeWrite.nIndex := cRampMode_AD.nIndex;
    fbCoeWrite.nSubIndex := cRampMode_AD.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.bRampMode_AD);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bRampMode_AD);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fGainFactor_AD <> stCoeParamsInternal.fGainFactor_AD THEN
    fbCoeWrite.nIndex := cGainFactor_AD.nIndex;
    fbCoeWrite.nSubIndex := cGainFactor_AD.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fGainFactor_AD);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fGainFactor_AD);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fRampTime_FIX1 <> stCoeParamsInternal.fRampTime_FIX1 THEN
    fbCoeWrite.nIndex := cRampTime_FIX1.nIndex;
    fbCoeWrite.nSubIndex := cRampTime_FIX1.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fRampTime_FIX1);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fRampTime_FIX1);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.bRampMode_FIX1 <> stCoeParamsInternal.bRampMode_FIX1 THEN
    fbCoeWrite.nIndex := cRampMode_FIX1.nIndex;
    fbCoeWrite.nSubIndex := cRampMode_FIX1.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.bRampMode_FIX1);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bRampMode_FIX1);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.bCtrlDir_FIX1 <> stCoeParamsInternal.bCtrlDir_FIX1 THEN
    fbCoeWrite.nIndex := cCtrlDir_FIX1.nIndex;
    fbCoeWrite.nSubIndex := cCtrlDir_FIX1.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.bCtrlDir_FIX1);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bCtrlDir_FIX1);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fPGain_FIX1 <> stCoeParamsInternal.fPGain_FIX1 THEN
    fbCoeWrite.nIndex := cPGain_FIX1.nIndex;
    fbCoeWrite.nSubIndex := cPGain_FIX1.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fPGain_FIX1);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPGain_FIX1);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fIGain_FIX1 <> stCoeParamsInternal.fIGain_FIX1 THEN
    fbCoeWrite.nIndex := cIGain_FIX1.nIndex;
    fbCoeWrite.nSubIndex := cIGain_FIX1.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fIGain_FIX1);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fIGain_FIX1);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fRampTime_FIX2 <> stCoeParamsInternal.fRampTime_FIX2 THEN
    fbCoeWrite.nIndex := cRampTime_FIX2.nIndex;
    fbCoeWrite.nSubIndex := cRampTime_FIX2.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fRampTime_FIX2);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fRampTime_FIX2);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.bRampMode_FIX2 <> stCoeParamsInternal.bRampMode_FIX2 THEN
    fbCoeWrite.nIndex := cRampMode_FIX2.nIndex;
    fbCoeWrite.nSubIndex := cRampMode_FIX2.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.bRampMode_FIX2);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bRampMode_FIX2);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.bCtrlDir_FIX2 <> stCoeParamsInternal.bCtrlDir_FIX2 THEN
    fbCoeWrite.nIndex := cCtrlDir_FIX2.nIndex;
    fbCoeWrite.nSubIndex := cCtrlDir_FIX2.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.bCtrlDir_FIX2);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bCtrlDir_FIX2);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fPGain_FIX2 <> stCoeParamsInternal.fPGain_FIX2 THEN
    fbCoeWrite.nIndex := cPGain_FIX2.nIndex;
    fbCoeWrite.nSubIndex := cPGain_FIX2.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fPGain_FIX2);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPGain_FIX2);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fIGain_FIX2 <> stCoeParamsInternal.fIGain_FIX2 THEN
    fbCoeWrite.nIndex := cIGain_FIX2.nIndex;
    fbCoeWrite.nSubIndex := cIGain_FIX2.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fIGain_FIX2);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fIGain_FIX2);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fRampTime_SP <> stCoeParamsInternal.fRampTime_SP THEN
    fbCoeWrite.nIndex := cRampTime_SP.nIndex;
    fbCoeWrite.nSubIndex := cRampTime_SP.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fRampTime_SP);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fRampTime_SP);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.bRampMode_SP <> stCoeParamsInternal.bRampMode_SP THEN
    fbCoeWrite.nIndex := cRampMode_SP.nIndex;
    fbCoeWrite.nSubIndex := cRampMode_SP.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.bRampMode_SP);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.bRampMode_SP);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fPGain_SP <> stCoeParamsInternal.fPGain_SP THEN
    fbCoeWrite.nIndex := cPGain_SP.nIndex;
    fbCoeWrite.nSubIndex := cPGain_SP.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fPGain_SP);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPGain_SP);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fPositionLimitOpen <> stCoeParamsInternal.fPositionLimitOpen THEN
    fbCoeWrite.nIndex := cPosLimitOpen.nIndex;
    fbCoeWrite.nSubIndex := cPosLimitOpen.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fPositionLimitOpen);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPositionLimitOpen);
    fbCoeWrite.bExecute := TRUE;
ELSIF stVcnVat590.stCoeParameters.fPositionLimitOpenControl <> stCoeParamsInternal.fPositionLimitOpenControl THEN
    fbCoeWrite.nIndex := cPosLimitOpenCtrl.nIndex;
    fbCoeWrite.nSubIndex := cPosLimitOpenCtrl.nSubIndex;
    fbCoeWrite.pSrcBuf := ADR(stVcnVat590.stCoeParameters.fPositionLimitOpenControl);
    fbCoeWrite.cbBufLen := SIZEOF(stVcnVat590.stCoeParameters.fPositionLimitOpenControl);
    fbCoeWrite.bExecute := TRUE;
END_IF

ftCoeWriteBusy(CLK:=fbCoeWrite.bBusy);
IF ftCoeWriteBusy.Q THEN
    fbCoeWrite.bExecute := FALSE;
    IF fbCoeWrite.bError THEN
            stVcnVat590.bCoeError := TRUE;
    END_IF
END_IF

fbCoeWrite();
END_ACTION

ACTION WriteToOutput:
(*Write to EPICS*)
stVcnVat590.bIlkOK := bExtIlkOK;
stVcnVat590.nPositionSP := REAL_TO_DINT(fCalcPosSP);
stVcnVat590.nPressureSP := REAL_TO_DINT(fCalcPresSP);
stVcnVat590.fReqPosition := fReqPos;
stVcnVat590.fReqPressure := fReqPres;
stVcnVat590.fPresSetLimit := fPresMaxLim;
stVcnVat590.fPosSetLimit := fUpperLimit;

(*Write to VAT valve*)
o_eCtrlModeSP := eValveModeSP;
o_nRawPositionSP := REAL_TO_DINT(fCalcPosSP);
o_nRawPressureSP := REAL_TO_DINT(fCalcPresSP);
o_nRawPressure := REAL_TO_DINT(fPressRaw);
o_stGenCtrlSP.Bit0ExecuteZero := stVcnVat590.bZero;
o_stGenCtrlSP.Bit4AccessMode := bRemAcsToggle;
END_ACTION

METHOD SetThrottle : BOOL
VAR_INPUT
    rUpperLimit     :       REAL;
END_VAR
stVcnVat590.fPosSetLimit := LIMIT(0, rUpperLimit, 100);
END_METHOD

METHOD ValveControl : BOOL
VAR_INPUT
    state : E_VCN_VAT590; // Set Control modes
END_VAR
stVcnVat590.eValveControl := state;
END_METHOD
Related:

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
    i_xVeto_Enable:BOOL:= FALSE; (*Set to TRUE if there is a Veto Valve to this VFS*)
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR
    xOPN_OK :BOOL;
    xERR_ExtFault :BOOL;
    xVeto: 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;
    rTrigOverrideOpen : R_TRIG;

    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;

    (*VETO Devices*)
    i_xVetoValveOpenDO AT%I*: BOOL;
    i_xVetoValveClosed 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


(*set Veto bit*)
IF (i_xVeto_Enable) THEN
    xVeto := (i_xVetoValveClosed AND NOT i_xVetoValveOpenDO);
    ELSE xVeto:=FALSE;
END_IF

//xVeto:= i_xVeto AND (i_xVetoValveClosed AND NOT i_xVetoValveOpenDO);
(* 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))  AND NOT(xVeto) AND NOT (tonOvrd.Q AND i_xOverrideMode) THEN
    xClose:= TRUE;
    xOpen:= FALSE;
    q_xVAC_FAULT_OK:= FALSE;
    xERR_ExtFault := TRUE; ;
    i_xCLS_SW := TRUE;
    q_xOPN_DO := FALSE;
    //i_xOPN_SW :=FALSE;
    xOpen:=FALSE;
ELSIF (i_xCLS_SW) THEN
    xClose:= TRUE;
    xOpen:= FALSE;
    q_xVAC_FAULT_OK:= FALSE;
    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) OR xVeto) 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 OR xVeto) ;

(* 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) OR xVeto)  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
*)
rTrigOverrideOpen(CLK:= i_xOverrideOpen);
tonDelOK(IN:=xOPN_OK, PT:=tDelOK);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND i_xOverrideOpen THEN
    i_xOverrideOpen :=FALSE;
    //if (i_xOpnLS) AND (i_xOverrideMode) THEN i_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 AND i_xVAC_FAULT_Reset) OR xVeto THEN
    xERR_ExtFault := FALSE;
    i_xVAC_FAULT_Reset:=FALSE;
END_IF


IF (i_xTrigger OR NOT i_xPress_OK) AND NOT xVeto THEN
    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
Related:

FB_VFS_2OO3

(* This function block implements basic functionality for Fast Shutter/Valves*)
(* NOTE: All 3 gauge cards with fast TTL output need to be installed in the same gauge controller*)
(* 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_2OO3 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 := 'VFS'; (*Device name for diagnostic*)
    i_xVeto_Enable : BOOL := FALSE; (*Set to TRUE if there is a Veto Valve to this VFS*)
END_VAR
VAR_IN_OUT
    io_fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR
    xOPN_OK : BOOL;         //internal variable OK to Open signal
    xERR_ExtFault : BOOL;   //internal variable External Fault signal
    xVeto : BOOL;           //internal variable Veto signal
    xTrigger : BOOL;        //internal trigger signal, output of 2oo3 logic
    xPress_OK : BOOL;       //internal signal indicating if at gauges are connected and OK
    //Fault counter triggers
    rtFault_IG1 : R_TRIG;
    rtFault_IG2 : R_TRIG;
    rtFault_IG3 : R_TRIG;
    //Reset Fault counter trigger
    rtResetFaultCounter : R_TRIG;

    //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;                    //Current pressure

    xFirstPass : BOOL := TRUE;            //Do something on first pass, after that set to FALSE

    rtOPN_SW : R_TRIG;                    //Detect rising endge on i_xOPN_SW input

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

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

    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 to the valve(s)*)
    i_xOpnLS        AT%I* : BOOL; //VFS Open limit switch input
    i_xClsLS        AT%I* : BOOL; //VFS Close limit switch input
    q_xClose_A      AT%Q* : BOOL; //3x Valve Close output signals (current req.)
    q_xClose_B      AT%Q* : BOOL; //3x Valve Close output signals (current req.)
    q_xClose_C      AT%Q* : BOOL; //3x Valve Close output signals (current req.)
    q_xOPN_DO       AT%Q* : BOOL; //Valve Open output signal
    (*3x Fast trigger Inputs from CC Gauge controller.
      Make sure all tri CC gauge cards are in the same controller*)
    i_xTriggerIG1 AT%I* : BOOL; //Fast trigger input from CC gauge 1
    i_xTriggerIG2 AT%I* : BOOL; //Fast trigger input from CC gauge 2
    i_xTriggerIG3 AT%I* : BOOL; //Fast trigger input from CC gauge 3

    (*VETO Devices*)
    i_xVetoValveOpenDO AT%I* : BOOL; (*Link to VFS Interface*)
    i_xVetoValveClosed AT%I* : BOOL; (*Link to VFS Interface*)

    (*Interface with external system*)
    i_e2OO3_MODE       AT%I* : E_2OO3_MODE; (*Link to VFS Interface, 2oo3 mode selector*)
    i_ResetFaultCounter AT%I* : BOOL; (*Link to VFS Interface, FaultCounter Reset*)
    i_xPress_OK_IG1    AT%I* : BOOL; (*Link to VFS Interface, IG.xPRESS_OK, to make sure that the device is connected*)
    i_xPress_OK_IG2    AT%I* : BOOL; (*Link to VFS Interface, IG.xPRESS_OK, to make sure that the device is connected*)
    i_xPress_OK_IG3    AT%I* : BOOL; (*Link to VFS Interface, IG.xPRESS_OK, to make sure that the device is connected*)
    i_xOPN_SW          AT%I* : BOOL; (*Link to VFS Interface, external open signal e.g epics*)
    i_xCLS_SW          AT%I* : BOOL; (*Link to VFS Interface, external close signal e.g epics*)
    i_xVAC_FAULT_Reset AT%I* : BOOL; (*Link to VFS Interface Valve Vacuum OK, is set to False when there is Vacuum Fault*)
    i_xOverrideMode    AT%I* : BOOL; (*Link to VFS Interface, To be linked to global override bit. This Overrides Vacuum logic only, EPS, MPS and PMPS are still enforces*)
    i_xOverrideOpen    AT%I* : BOOL; (*Link to VFS Interface*)
    q_xTrigger         AT%Q* : BOOL; (*Link to VFS Interface*)
    q_xVFS_Open        AT%Q* : BOOL; (*Link to VFS Interface*)
    q_xVFS_Closed      AT%Q* : BOOL; (*Link to VFS Interface*)
    q_xVAC_FAULT_OK    AT%Q* : BOOL; (*Link to VFS Interface, Valve Vacuum OK, is set to False when there is Vacuum Fault*)
    q_xMPS_OK          AT%Q* : BOOL; (*Link to VFS Interface, MPS Fault OK, is set when the Valve is Open and there is no trigger*)
    q_eVFS_State       AT%Q* : E_VGC; (*Link to VFS Interface*)
    q_nFltCount_IG1    AT%Q* : UINT; (*Link to VFS Interface*)
    q_nFltCount_IG2    AT%Q* : UINT; (*Link to VFS Interface*)
    q_nFltCount_IG3    AT%Q* : UINT; (*Link to VFS Interface*)
END_VAR
VAR PERSISTENT
    nFaultCounter_IG1 : UINT;  //Fault counter for IG1
    nFaultCounter_IG2 : UINT;  //Fault counter for IG2
    nFaultCounter_IG3 : UINT;  //Fault counter for IG3
END_VAR
(*Fast Shutter/Valve Function Block*)
(*M.Ghaly Feb 2019*)
(*J. Govednik Jan 2022 Adding 2OO3 logic*)

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

(*set Veto bit*)
IF (i_xVeto_Enable) THEN
    xVeto := (i_xVetoValveClosed AND NOT i_xVetoValveOpenDO);
    ELSE xVeto := FALSE;
END_IF

(*User will be able to select gauge channel or 2oo3 function*)
CASE i_e2OO3_MODE OF
    E_2OO3_MODE.MODE_2OO3:
            (*2oo3 logic*)
            //First let's verify that at least 2 gauges are connected
            xPress_OK := (i_xPress_OK_IG1 AND i_xPress_OK_IG2) OR
                             (i_xPress_OK_IG1 AND i_xPress_OK_IG3) OR
                             (i_xPress_OK_IG2 AND i_xPress_OK_IG3);

            //Each trigger input validated with Press OK bit which indicates if the gauge is connected or disconnected
            IF xPress_OK THEN
                    xTrigger := ((i_xTriggerIG1 OR NOT i_xPress_OK_IG1) AND (i_xTriggerIG2 OR NOT i_xPress_OK_IG2)) OR
                            ((i_xTriggerIG1 OR NOT i_xPress_OK_IG1) AND (i_xTriggerIG3 OR NOT i_xPress_OK_IG3)) OR
                            ((i_xTriggerIG2 OR NOT i_xPress_OK_IG2) AND (i_xTriggerIG3 OR NOT i_xPress_OK_IG3));
                    (*
                    xTrigger := ((i_xTriggerIG1 AND i_xPress_OK_IG1) AND (i_xTriggerIG2 AND i_xPress_OK_IG2)) OR
                            ((i_xTriggerIG1 AND i_xPress_OK_IG1) AND (i_xTriggerIG3 AND i_xPress_OK_IG3)) OR
                            ((i_xTriggerIG2 AND i_xPress_OK_IG2) AND (i_xTriggerIG3 AND i_xPress_OK_IG3));
                    *)
            ELSE
                    xTrigger := TRUE;
            END_IF
    E_2OO3_MODE.IG1:
            xPress_OK := i_xPress_OK_IG1;
            xTrigger := i_xTriggerIG1 OR NOT i_xPress_OK_IG1;
    E_2OO3_MODE.IG2:
            xPress_OK := i_xPress_OK_IG2;
            xTrigger := i_xTriggerIG2 OR NOT i_xPress_OK_IG2;
    E_2OO3_MODE.IG3:
            xPress_OK := i_xPress_OK_IG3;
            xTrigger := i_xTriggerIG3 OR NOT i_xPress_OK_IG3;
END_CASE;

//xVeto:= i_xVeto AND (i_xVetoValveClosed AND NOT i_xVetoValveOpenDO);
(*Input Trigger to close the Fast Shutter*)
(*Neighboring Valves Should also be triggered to Close*)
//IF ((xTrigger) OR NOT (i_xPress_OK) OR NOT (i_xExt_OK))  AND NOT (xVeto) AND NOT (tonOvrd.Q AND i_xOverrideMode) THEN
IF ((xTrigger) OR NOT (i_xExt_OK))  AND NOT (xVeto) AND NOT (tonOvrd.Q AND i_xOverrideMode) THEN
    xClose := TRUE;
    xOpen := FALSE;
    q_xVAC_FAULT_OK := FALSE;
    xERR_ExtFault := TRUE;
    i_xCLS_SW := TRUE;
    q_xOPN_DO := FALSE;
    //i_xOPN_SW :=FALSE;
ELSIF (i_xCLS_SW) THEN
    xClose := TRUE;
    xOpen := FALSE;
    q_xVAC_FAULT_OK:= FALSE;
    i_xCLS_SW := TRUE;
    q_xOPN_DO := FALSE;
    //i_xOPN_SW :=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

//Hold Valve close signals to TRUE until limit swith is hit
IF (xClose) AND (NOT i_xClsLS) THEN
    q_xClose_A := TRUE;
    q_xClose_B := TRUE;
    q_xClose_C := TRUE;
END_IF

//Set Valve close signals to FALSE when close timer expires or close limit switch is closed
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

//Set Valve open signal to FALSE when open timer expires or open limit switch is closed
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 xTrigger) OR xVeto) 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 xTrigger OR xVeto) ;

(*When the valve  is open MPS is OK*)
//q_xMPS_OK := i_xOpnLS AND ((NOT xTrigger AND i_xPress_OK) OR xVeto)  AND NOT(i_xCLS_SW);
q_xMPS_OK := i_xOpnLS AND (NOT xTrigger OR xVeto) 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
*)
rTrigOverrideOpen (CLK := i_xOverrideOpen); // Not used

tonDelOK (IN := xOPN_OK, PT := tDelOK);
rtOK (CLK := tonDelOK.Q);

IF rtOK.Q AND i_xOverrideOpen THEN
    i_xOverrideOpen := FALSE;
    //if (i_xOpnLS) AND (i_xOverrideMode) THEN i_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 (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);
OpenTimer (IN := i_xOpnLS, PT := OpenTimDur);
tOPNtimeout (IN := q_xOPN_DO, PT := tOPNTimeOutDuration);
tCLStimeout (IN := q_xClose_A, PT := tCLSTimeOutDuration);

//Detecting rising edge for i_xOPN_SW (epics command)
rtOPN_SW (CLK := i_xOPN_SW);

(*Gauge fault counters*)
rtResetFaultCounter(CLK := i_ResetFaultCounter);
IF rtResetFaultCounter.Q THEN
    nFaultCounter_IG1 := 0;
    nFaultCounter_IG2 := 0;
    nFaultCounter_IG3 := 0;
END_IF
//IG1
rtFault_IG1(CLK := i_xTriggerIG1 AND NOT xTrigger);
IF rtFault_IG1.Q THEN
    nFaultCounter_IG1 := nFaultCounter_IG1 + 1;
END_IF
//IG2
rtFault_IG2(CLK := i_xTriggerIG2 AND NOT xTrigger);
IF rtFault_IG2.Q THEN
    nFaultCounter_IG2 := nFaultCounter_IG2 + 1;
END_IF
//IG3
rtFault_IG3(CLK := i_xTriggerIG3 AND NOT xTrigger);
IF rtFault_IG3.Q THEN
    nFaultCounter_IG3 := nFaultCounter_IG3 + 1;
END_IF

(*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 xTrigger AND xPress_OK AND i_xVAC_FAULT_Reset) OR xVeto THEN
    xERR_ExtFault := FALSE;
    i_xVAC_FAULT_Reset := FALSE;
END_IF


IF (xTrigger OR NOT xPress_OK) AND NOT xVeto THEN
    i_xVAC_FAULT_Reset := FALSE;
END_IF
END_ACTION

ACTION IO:
q_xTrigger := xTrigger;
q_xVFS_Closed := i_xClsLS;
q_xVFS_Open := i_xOpnLS;

q_nFltCount_IG1 := nFaultCounter_IG1;
q_nFltCount_IG2 := nFaultCounter_IG2;
q_nFltCount_IG3 := nFaultCounter_IG3;
END_ACTION
Related:

FB_VFS_2OO3Interface

(*Used as soft IO mapping to create a psuedo valve to communicate over two task on the same PLC.*)
(*NOTE: All 3 gauge cards with fast TTL output need to be installed in the same gauge controller*)
(*For FAST shutter control*)
FUNCTION_BLOCK FB_VFS_2OO3Interface EXTENDS FB_Valve
VAR_INPUT
    (*Make sure all three CC gauge cards are in the same controller*)
    IG1 : ST_VG; // The MKS422 Cold Cathode Data Structure for CC gauge 1
    IG2 : ST_VG; // The MKS422 Cold Cathode Data Structure for CC gauge 2
    IG3 : ST_VG; // The MKS422 Cold Cathode Data Structure for CC gauge 3
    Veto_Valve : ST_VGC; // The VGC structure for the Veto Valve
    {attribute 'pytmc' := '
        pv: VFS_2OO3_MODE
        io: io
    '}
    i_e2OO3_MODE : E_2OO3_MODE; //2oo3 mode selector as EPICS Command or Input for global variable
    {attribute 'pytmc' := '
        pv: VFS_RESET_FAULT_COUNTER
        io: io
    '}
    i_ResetFaultCounter : BOOL; //Reset Fault counter as EPCIS Command or Input for global reset button
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
    rtTriggerVetoed : R_TRIG;
    rtTriggered : R_TRIG;
    tonOvrd : TON;
    tOvrd : TIME := T#10s;
    rtOK : R_TRIG;
    tonDelOK : TON;
    xOPN_OK : BOOL;
    tDelOK  : TIME := T#60S;
    xPress_OK : BOOL; //internal signal indicating if at gauges are connected and OK

    //Reset Fault counter trigger
    rtResetFaultCounter : R_TRIG;

    (*outputs*)
    q_e2OO3_MODE AT%Q* : E_2OO3_MODE; //2oo3 mode selector, connect to FB_VFS_2OO3 mode selector input
    q_ResetFaultCounter AT%Q* : BOOL; //Reset Fault counter, connect to FB_VFS_2OO3 reset fault counter input
    q_xPRESS_OK_IG1 AT%Q* : BOOL;
    q_xPRESS_OK_IG2 AT%Q* : BOOL;
    q_xPRESS_OK_IG3 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;

    (*VETO Devices*)
    q_xVetoValveOpenDO AT%Q* : BOOL;
    q_xVetoValveClosed 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*)
    {attribute 'pytmc' := '
        pv: FLT_COUNT_IG1
        io: i
    '}
    i_nFltCount_IG1 AT%I* : UINT;      //Fault counter for IG1
    {attribute 'pytmc' := '
        pv: FLT_COUNT_IG2
        io: i
    '}
    i_nFltCount_IG2 AT%I* : UINT;      //Fault counter for IG2
    {attribute 'pytmc' := '
        pv: FLT_COUNT_IG3
        io: i
    '}
    i_nFltCount_IG3 AT%I* : UINT;      //Fault counter for IG3
END_VAR
(*Soft IO Mapping*)
q_xPRESS_OK_IG1 := IG1.xPRESS_OK;
q_xPRESS_OK_IG2 := IG2.xPRESS_OK;
q_xPRESS_OK_IG3 := IG3.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);
tonDelOK(IN:=xOPN_OK, PT:=tDelOK);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND q_xOverrideOpen THEN
    q_xOverrideOpen :=FALSE;
    //if (i_xOpnLS) AND (i_xOverrideMode) THEN i_xOPN_SW := TRUE; END_IF //for seamless transition
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF
(*OPN OK evaluation bit *)
xOPN_OK := (i_xVAC_FAULT_OK) AND (NOT i_xTrigger OR (q_xVetoValveClosed AND NOT q_xVetoValveOpenDO));

(*Reset Fault counter, q_ResetFaultCounter output is TRUE for one plc cycle *)
rtResetFaultCounter(CLK := i_ResetFaultCounter);
IF rtResetFaultCounter.Q THEN
    i_ResetFaultCounter := FALSE;
    q_ResetFaultCounter := TRUE;
ELSE
    q_ResetFaultCounter := FALSE;
END_IF

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

//Log when the trigger is vetoed
rtTriggerVetoed(CLK:= (i_xTrigger AND (NOT q_xVetoValveOpenDO AND q_xVetoValveClosed)));
IF rtTriggerVetoed.Q THEN fbLogger(sMsg:='Valve triggered while veto device is in', eSevr:=TcEventSeverity.Warning); END_IF

rtTriggered(CLK:= (i_xTrigger AND ( q_xVetoValveOpenDO AND NOT q_xVetoValveClosed)));
IF rtTriggered.Q THEN fbLogger(sMsg:='Fast valve triggered!', eSevr:=TcEventSeverity.Critical); 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 := CONCAT(CONCAT(CONCAT(IG1.sPath,','),CONCAT(IG2.sPath,',')),IG3.sPath);

(*update values*)
iq_stValve.xOPN_OK := xOPN_OK;

(*Veto*)
(*VETO Devices*)
q_xVetoValveOpenDO := Veto_Valve.q_xOPN_DO;
q_xVetoValveClosed := Veto_Valve.i_xClsLS;

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

//Veto device name
iq_stValve.sVetoDeviceName := Veto_Valve.sDevName;

(*2oo3 mode selector*)
q_e2OO3_MODE := i_e2OO3_MODE;
END_ACTION
Related:

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
    Veto_Valve: ST_VGC;// The VGC structure for the Veto Valve
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
    rtTriggerVetoed : R_TRIG;
    rtTriggered : R_TRIG;
    tonOvrd :       TON;
    tOvrd   :       TIME := T#10s;
    rtOK    :       R_TRIG;
    tonDelOK : TON;
    xOPN_OK : BOOL;
    tDelOK  :       TIME := T#60S;

    (*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;

    (*VETO Devices*)
    q_xVetoValveOpenDO AT%Q*: BOOL;
    q_xVetoValveClosed 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);
tonDelOK(IN:=xOPN_OK, PT:=tDelOK);
rtOK(CLK:=tonDelOK.Q);
IF rtOK.Q AND q_xOverrideOpen THEN
    q_xOverrideOpen :=FALSE;
    //if (i_xOpnLS) AND (i_xOverrideMode) THEN i_xOPN_SW := TRUE; END_IF //for seamless transition
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF
(*OPN OK evaluation bit *)
xOPN_OK := (i_xVAC_FAULT_OK) AND (NOT i_xTrigger OR (q_xVetoValveClosed AND NOT q_xVetoValveOpenDO)) ;
(* 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

//Log when the trigger is vetoed
rtTriggerVetoed(CLK:= (NOT q_xPRESS_OK OR i_xTrigger) AND (NOT q_xVetoValveOpenDO AND q_xVetoValveClosed));
IF rtTriggerVetoed.Q THEN fbLogger(sMsg:='Valve triggered while veto device is in', eSevr:=TcEventSeverity.Warning); END_IF

rtTriggered(CLK:= (NOT q_xPRESS_OK OR i_xTrigger) AND ( q_xVetoValveOpenDO AND NOT q_xVetoValveClosed));
IF rtTriggered.Q THEN fbLogger(sMsg:='Fast valve triggered!', eSevr:=TcEventSeverity.Critical); 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;

(*update values*)
iq_stValve.xOPN_OK := xOPN_OK;

(*Veto*)
(*VETO Devices*)
q_xVetoValveOpenDO := Veto_Valve.q_xOPN_DO;
q_xVetoValveClosed := Veto_Valve.i_xClsLS;

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

//Veto device name
iq_stValve.sVetoDeviceName := Veto_Valve.sDevName;
END_ACTION
Related:

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

VAR PERSISTENT
    rAT_VAC_SP      :       REAL;
    rHYST_PERC      :       REAL;
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;
    iq_stValve.sDevName := 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();

(*Load or save the persistent variables*)
ACT_Persistent();


(*FAST FAULT*)
FFO(i_xOK := xMPS_OK,
    i_xReset := i_xReset,
    i_xAutoReset :=TRUE,
    i_DevName := i_sDevName,
    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_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rAT_VAC_SP <> 0) THEN
            iq_stValve.rAT_VAC_SP := rAT_VAC_SP;
    END_IF;
    IF (rHYST_PERC <> 0) THEN
                    iq_stValve.rHYST_PERC := rHYST_PERC;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stValve.rAT_VAC_SP = rAT_VAC_SP) THEN
    rAT_VAC_SP :=iq_stValve.rAT_VAC_SP;
END_IF;

IF NOT (iq_stValve.rHYST_PERC = rHYST_PERC) THEN
    rHYST_PERC:= iq_stValve.rHYST_PERC;
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_sDeviceName:=i_sDevName,
     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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
            value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

FB_VGC_2S

(* This function block implements basic functionality for Isolation Gate Valves with 2 pressure setpoints*)
(* 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-setpoints.
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_2S 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_2S; (* 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_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

VAR PERSISTENT
    rAT_VAC_SP      :       REAL;
    rHYST_PERC      :       REAL;
    rAT_VAC_SP_DS : REAL;
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;
    iq_stValve.rAT_VAC_SP_DS := 0.001;
    iq_stValve.rAT_VAC_SP := 0.001;
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_DS <> iq_stValve.rAT_VAC_SP_DS OR xFirstPass THEN
    iq_stValve.rAT_VAC_SP_LAST_DS := iq_stValve.rAT_VAC_SP_DS;
    iq_stValve.rAT_VAC_HYS_DS := iq_stValve.rHYST_PERC * iq_stValve.rAT_VAC_SP_DS;
END_IF

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

// Setpoint one Upstream gauge
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
//setpoint two Downstream gauge
iq_stValve.rAT_VAC_HYS_DS := LIMIT(0, iq_stValve.rAT_VAC_HYS_DS, iq_stValve.rAT_VAC_SP_DS);
IF iq_stValve.rAT_VAC_SP_DS <> 0 THEN
    IF iq_stValve.rHYST_PERC <> (iq_stValve.rAT_VAC_HYS_DS / iq_stValve.rAT_VAC_SP_DS) THEN
            iq_stValve.rHYST_PERC := LIMIT(0, (iq_stValve.rAT_VAC_HYS_DS / iq_stValve.rAT_VAC_SP_DS) ,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_DS 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_DS 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();

(*Load or save the persistent variables*)
ACT_Persistent();

(*FAST FAULT*)
FFO(i_xOK := xMPS_OK,
    i_xReset := i_xReset,
    i_xAutoReset :=TRUE,
    i_DevName := i_sDevName,
    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_Persistent:
(*On first PLC pass, load the persistent value into the structrue variable*)
IF (SUPER^.bRestorePersistentData) THEN
    SUPER^.bRestorePersistentData   := FALSE;
    IF (rAT_VAC_SP <> 0) THEN
            iq_stValve.rAT_VAC_SP := rAT_VAC_SP;
    END_IF;
    IF (rHYST_PERC <> 0) THEN
                    iq_stValve.rHYST_PERC := rHYST_PERC;
    END_IF;
    IF (rAT_VAC_SP_DS <> 0) THEN
            iq_stValve.rAT_VAC_SP_DS := rAT_VAC_SP_DS;
    END_IF;
END_IF
(*Check if a new value has been written in the structure variable copy it to the persistent variable*)
IF NOT (iq_stValve.rAT_VAC_SP = rAT_VAC_SP) THEN
    rAT_VAC_SP := iq_stValve.rAT_VAC_SP;
END_IF;

IF NOT (iq_stValve.rAT_VAC_HYS = rHYST_PERC) THEN
    rHYST_PERC := iq_stValve.rAT_VAC_HYS;
END_IF;

IF NOT (iq_stValve.rAT_VAC_SP_DS = rAT_VAC_SP_DS) THEN
    rAT_VAC_SP_DS := iq_stValve.rAT_VAC_SP_DS;
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_sDeviceName:=i_sDevName,
     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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
            value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

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'}
{attribute 'obsolete' := 'FB_VGC_AP has been deprecated and is not valid!'}
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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
            value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

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'}
{attribute 'obsolete' := 'FB_VGC_EBD has been deprecated and is not valid!'}
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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
            value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

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

METHOD M_Check_State
VAR_INPUT
END_VAR

END_METHOD

METHOD M_INIT : BOOL
VAR//_INPUT

END_VAR
TEST('VGC_Startup');
This^.US_IG.rPRESS := 1E-9;
This^.US_IG.xPRESS_OK := TRUE;
This^.US_IG.xAT_VAC := TRUE;

This^.DS_IG.rPRESS := 1E-9;
This^.DS_IG.xPRESS_OK := TRUE;

(*This^.VGC(
    i_stUSG:= This^.US_IG,
    i_stDSG:= This^.DS_IG ,
    i_xDis_DPIlk:= FALSE,
    i_xEPS_OK:= TRUE ,
    //i_xPMPS_OK:= TRUE,
    i_xExt_OK:= TRUE,
    i_xOverrideMode:= ,
    i_xReset:= ,
    iq_stValve=> ,
    xMPS_OK=> ,
    io_fbFFHWO:= This^.io_fbFFHWO,
    fbArbiter:=g_fbArbiter);
*)
AssertFalse(This^.VGC.iq_stValve.pv_xOPN_SW,'VGC pv_xOPN_SW is set to true');
AssertFalse(VGC.iq_stValve.q_xOPN_DO,'VGC q_xOPN_DO is set to true');
TEST_FINISHED_NAMED('VGC_Startup');
END_METHOD

METHOD M_Interlock : BOOL
VAR//_INPUT

END_VAR
// Test interlock is active and attempting to open valve
TEST('VGC_Interlock_Active');
This^.US_IG.xPRESS_OK := FALSE;
This^.DS_IG.xPRESS_OK := FALSE;
VGC.M_Set_OPN_SW(TRUE);
// run the function block one more time
(*VGC(
    i_stUSG:= US_IG,
    i_stDSG:= DS_IG ,
    i_xDis_DPIlk:= FALSE,
    i_xEPS_OK:= TRUE ,
    i_xPMPS_OK:= TRUE ,
    i_xExt_OK:= TRUE,
    i_xOverrideMode:= ,
    i_xReset:= ,
    iq_stValve=> ,
    xMPS_OK=> ,
    io_fbFFHWO:= io_fbFFHWO);*)
AssertFalse(VGC.iq_stValve.pv_xOPN_SW,'VGC pv_xOPN_SW is set to true');
AssertFalse(VGC.iq_stValve.q_xOPN_DO,'VGC q_xOPN_DO is set to true');
TEST_FINISHED_NAMED('VGC_Interlock_Active');
// Test interlock is not active and attempting to open valve
TEST('VGC_Interlock_OK');
This^.US_IG.xPRESS_OK := TRUE;
This^.DS_IG.xPRESS_OK := TRUE;
VGC.M_Set_OPN_SW(TRUE);
// run the function block one more time
(*VGC(
    i_stUSG:= US_IG,
    i_stDSG:= DS_IG ,
    i_xDis_DPIlk:= FALSE,
    i_xEPS_OK:= ,
    i_xPMPS_OK:= ,
    i_xExt_OK:= ,
    i_xOverrideMode:= ,
    i_xReset:= ,
    iq_stValve=> ,
    xMPS_OK=> ,
    io_fbFFHWO:= io_fbFFHWO);*)
AssertTrue(VGC.iq_stValve.pv_xOPN_SW,'VGC pv_xOPN_SW is set to false');
AssertTrue(VGC.iq_stValve.q_xOPN_DO,'VGC q_xOPN_DO is set to false');
TEST_FINISHED_NAMED('VGC_Interlock_OK');
// Test interlock ok lost while valve is open
TEST('VGC_Interlock_Lost');
This^.US_IG.xPRESS_OK := False;
This^.DS_IG.xPRESS_OK := TRUE;
// run the function block one more time
(*VGC(
    i_stUSG:= US_IG,
    i_stDSG:= DS_IG ,
    i_xDis_DPIlk:= FALSE,
    i_xEPS_OK:= ,
    i_xPMPS_OK:= ,
    i_xExt_OK:= ,
    i_xOverrideMode:= ,
    i_xReset:= ,
    iq_stValve=> ,
    xMPS_OK=> ,
    io_fbFFHWO:= io_fbFFHWO);*)
AssertFalse(VGC.iq_stValve.pv_xOPN_SW,'VGC pv_xOPN_SW is set to true');
AssertFalse(VGC.iq_stValve.q_xOPN_DO,'VGC q_xOPN_DO is set to true');
TEST_FINISHED_NAMED('VGC_Interlock_Lost');
END_METHOD
Related:

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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
    value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

FB_VRC_DA

(* This Function Block Implements Basic Functionality for dual acting valves. EPs is implemented so that there is interlock Open and Interlock close,
This function block is interloked by an input . 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_DA EXTENDS FB_Valve
VAR_IN_OUT

END_VAR
VAR_INPUT
    i_OPN_OK : BOOL; //Connect to EPS Interlock logic condition, Set to True if the valve is not interlocked
    i_CLS_OK : BOOL; //Connect to EPS Interlock logic condition, Set to True if the valve is not interlocked
    ibCntrlHold:BOOL; (* Control Signal must retain its value, False in case of pulse control*)

    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_NO;
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;
    (*Limit switch latch timer*)
    tLimitSwitchLatchDuration: TIME:=T#1S;
    tOPNLimitSwitch:TON;
    tCLSLimitSwitch:TON;
    tAction2 : R_TRIG;
    (*IO*)
    i_xOpnLS        AT%I*: BOOL;
    i_xClsLS        AT%I*: BOOL;
    q_xOPN_DO       AT%Q*: BOOL;
    q_xCLS_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;
    iq_stValve.pv_xCLS_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 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



(* Can't have both open and close True at the same time. Reset both*)
If (iq_stValve.pv_xOPN_SW) AND (iq_stValve.pv_xCLS_SW) THEN
    iq_stValve.pv_xOPN_SW := FALSE;
    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
*)


// Release the Force Open bit when the system override is false
IF NOT(i_xOverrideMode) THEN
    iq_stValve.pv_xOvrdOpn :=FALSE;
    iq_stValve.pv_xOvrdCls := FALSE;
END_IF
//Override timer
tonOvrd(IN:=(iq_stValve.pv_xOvrdOpn OR iq_stValve.pv_xOvrdCls), PT:=tOvrd);

(* Here's where the valve Actuates *)
(*Evaluate EPS interlocks and the override*)
iq_stValve.q_xCLS_DO  := (i_CLS_OK AND iq_stValve.pv_xCLS_SW AND NOT iq_stValve.pv_xOPN_SW) OR (tonOvrd.Q AND iq_stValve.pv_xOvrdCls AND i_xOverrideMode);
iq_stValve.q_xOPN_DO  := (i_OPN_OK AND iq_stValve.pv_xOPN_SW AND NOT iq_stValve.pv_xCLS_SW) OR (tonOvrd.Q AND iq_stValve.pv_xOvrdOpn AND i_xOverrideMode) ;

(*evaluate Valve open external interlock*)

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

IF NOT iq_stValve.xCLS_OK and NOT tonOvrd.Q THEN
    iq_stValve.pv_xCLS_SW := FALSE;
    iq_stValve.eVGC_State := ERR_ExtFault;
END_IF

IF (iq_stValve.q_xCLS_DO) AND (iq_stValve.xCLS_OK) THEN
    iq_stValve.eVGC_State := AtVacuum;
END_IF


(*Reset the Control signal when command has been executed and give time to ensure the actuator is fully seated in either direction*)
(* for pulse control*)
IF (NOT ibCntrlHold) THEN
   IF (iq_stValve.pv_xOPN_SW AND iq_stValve.i_xOpnLS AND tOPNLimitSwitch.Q ) THEN iq_stValve.pv_xOPN_SW := FALSE; END_IF
   IF (iq_stValve.pv_xCLS_SW AND iq_stValve.i_xClsLS AND tCLSLimitSwitch.Q ) THEN iq_stValve.pv_xCLS_SW := FALSE; END_IF
END_IF

///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:= iq_stValve.q_xCLS_DO, PT := tTimeOutDuration);
tCLSLimitSwitch(IN:= iq_stValve.i_xClsLS, PT := tLimitSwitchLatchDuration);
tOPNLimitSwitch(IN:= iq_stValve.i_xOpnLS, PT := tLimitSwitchLatchDuration);

(*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;
iq_stValve.xCLS_OK := i_CLS_OK;
iq_stValve.xOPN_OK := i_OPN_OK;
(*outputs*)
q_xOPN_DO:= iq_stValve.q_xOPN_DO;
q_xCLS_DO:= iq_stValve.q_xCLS_DO;
END_ACTION

ACTION ACT_Logger:
//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

tAction2(CLK:= iq_stValve.q_xCLS_DO);
IF tAction.Q THEN fbLogger(sMsg:='Valve commanded closed', 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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

METHOD M_Set_CLS_SW : BOOL
VAR_INPUT
    value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := NOT (value);
This^.iq_stValve.pv_xCLS_SW := value;
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
    value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
This^.iq_stValve.pv_xCLS_SW := NOT (value);
END_METHOD
Related:

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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
    value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

FB_VRC_NC_ClsLS

(* This Function Block Implements Basic Functionality for ChemRIX NC valve, which has one NC LS mounting at Open position.
Valve considers CLOSED when the LS is closed.
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_NC_ClsLS 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


IF NOT iq_stValve.i_xClsLS AND iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=OPEN;
ELSIF NOT iq_stValve.i_xClsLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=INVALID;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=CLOSED;
ELSIF iq_stValve.i_xClsLS AND iq_stValve.q_xOPN_DO THEN
    iq_stValve.eState:=INVALID;
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 iq_stValve.i_xClsLS 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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
    value:BOOL;
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

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

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


(*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
    iq_stValve.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 NOT iq_stValve.q_xCLS_DO THEN
    iq_stValve.eState:=OPEN;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS AND iq_stValve.q_xCLS_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
    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 AND iq_stValve.pv_xOvrdCls THEN
    iq_stValve.pv_xOvrdCls :=FALSE;
    if (iq_stValve.eState=CLOSED) AND (i_xOverrideMode) THEN iq_stValve.pv_xCLS_SW := TRUE; END_IF
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF

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

(* Here's where the valve Actuates *)
q_xCLS_DO := ((iq_stValve.xCLS_OK  AND iq_stValve.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:= NOT iq_stValve.q_xCLS_DO, PT := tTimeOutDuration );
tCLStimeout(IN:= iq_stValve.q_xCLS_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*)
iq_stValve.q_xCLS_DO := q_xCLS_DO;
END_ACTION

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_CLS_SW : BOOL
VAR_INPUT
            value:BOOL;
END_VAR
This^.iq_stValve.pv_xCLS_SW := value;
END_METHOD
Related:

FB_VRC_NO_FFO

(* This function block is different from the regular VRC in that CLOSING must be permitted. *)
FUNCTION_BLOCK FB_VRC_NO_FFO EXTENDS FB_VRC_NO
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
    fbArbiter: FB_Arbiter();
END_VAR
VAR_INPUT
    i_xPMPS_OK:     BOOL    ; (*Set to True To switch off the bptm and PMPS Arbiter*)
    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_NO'; // Device name for diagnostic
    i_nTransitionRootID: UDINT; //A unique transition Root ID that is equal to or greater than 1000i_xIsAperture:BOOL :=FALSE; // Set tp True if this is an Aperture Valve, the MPS Fault will trip only when moving.
END_VAR
VAR_OUTPUT

END_VAR
VAR

    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 safe state',
        i_TypeCode := 16#1010);
    xMPS_OK:        BOOL; (*MPS Fast OK, is set when the Valve is Open*)

END_VAR
(*VRC_NO*)

(* 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
    iq_stValve.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 NOT iq_stValve.q_xCLS_DO THEN
    iq_stValve.eState:=OPEN;
ELSIF iq_stValve.i_xClsLS AND NOT iq_stValve.i_xOpnLS AND iq_stValve.q_xCLS_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
    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 AND iq_stValve.pv_xOvrdCls THEN
    iq_stValve.pv_xOvrdCls :=FALSE;
    if (iq_stValve.eState=CLOSED) AND (i_xOverrideMode) THEN iq_stValve.pv_xCLS_SW := TRUE; END_IF
    //Log
    fbLogger(sMsg:='Override expired', eSevr:=TcEventSeverity.Warning);
END_IF

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

(* Here's where the valve Actuates *)
q_xCLS_DO := ((iq_stValve.xCLS_OK  AND iq_stValve.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:= NOT iq_stValve.q_xCLS_DO, PT := tTimeOutDuration );
tCLStimeout(IN:= iq_stValve.q_xCLS_DO, PT := tTimeOutDuration);


(*Soft IO Mapping*)
ACT_IO();

// Log States and triggers
ACT_Logger();




(*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.xCLS_OK) THEN
    iq_stValve.pv_xCLS_SW:= FALSE; // Reset switch after inlk is lost
    iq_stValve.q_xCLS_DO := (iq_stValve.pv_xCLS_SW  AND iq_stValve.xCLS_OK) OR (tonOvrd.Q AND i_xOverrideMode);
ELSIF xPMPS_OK THEN
    iq_stValve.q_xCLS_DO := (iq_stValve.pv_xCLS_SW  AND iq_stValve.xCLS_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 NOT q_xCLS_DO) XOR (i_xClsLS  AND q_xCLS_DO);
ELSE
    (* When the valve  is open MPS is OK*)
    xMPS_OK := i_xOpnLS AND NOT i_xClsLS AND q_xCLS_DO;
END_IF

(*FAST FAULT*)
FFO(i_xOK := xMPS_OK,
    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. := i_xEXT_OK;
iq_stValve.xOverrideMode := i_xOverrideMode;
(*outputs*)
//q_xOPN_DO:= iq_stValve.q_xOPN_DO;
iq_stValve.q_xCLS_DO:= q_xCLS_DO;
END_ACTION

ACTION ACT_PMPS:
bMoving := (iq_stValve.pv_xCLS_SW AND NOT iq_stValve.i_xClsLS) XOR (NOT iq_stValve.pv_xCLS_SW AND NOT i_xOpnLS);
bDone := (NOT iq_stValve.pv_xCLS_SW AND iq_stValve.i_xOpnLS) XOR ( iq_stValve.pv_xCLS_SW AND i_xClsLS);
If (i_xIsAperture ) OR NOT (iq_stValve.pv_xCLS_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(NOT iq_stValve.pv_xCLS_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

METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := (This^.iq_stValve.eState = E_ValvePositionState.CLOSED);
END_METHOD

METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := (This^.iq_stValve.eState = E_ValvePositionState.OPEN);
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_CLS_SW : BOOL
VAR_INPUT
            value:BOOL;
END_VAR
This^.iq_stValve.pv_xCLS_SW := value;
END_METHOD
Related:

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

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

METHOD M_Check_State
VAR_INPUT
END_VAR

END_METHOD

METHOD M_INIT : BOOL
VAR//_INPUT


    Interlock_OK :BOOL := TRUE;

    ex: INT;
END_VAR
TEST('VRC_Startup');
//__TRY
VRC(i_xExtILK_OK:= Interlock_OK, i_xOverrideMode:= , iq_stValve=> );
AssertFalse(VRC.iq_stValve.q_xOPN_DO,'VRC q_xOPN_DO is set to true');
AssertFalse(VRC.iq_stValve.pv_xOPN_SW,'VRC pv_xOPN_SW is set to true');
//__CATCH(ex)
//__FINALLY
TEST_FINISHED_NAMED('VRC_Startup');
//__ENDTRY
END_METHOD

METHOD M_Interlock
VAR
    US_IG : ST_VG;
    DS_IG : ST_VG;

    VRC: FB_VRC;
END_VAR
TEST('VRC_Interlock_Active');
VRC.M_Set_OPN_SW (TRUE);
VRC(i_xExtILK_OK:= FALSE, i_xOverrideMode:= , iq_stValve=> );
AssertFalse(VRC.iq_stValve.pv_xOPN_SW,'VRC open_SW is set to true');
AssertFalse(VRC.iq_stValve.q_xOPN_DO,'VRC open DO is set to true');
TEST_FINISHED_NAMED('VRC_Interlock_Active');

TEST('VRC_Interlock_OK');
VRC.M_Set_OPN_SW (TRUE);
VRC(i_xExtILK_OK:= TRUE, i_xOverrideMode:= , iq_stValve=> );
AssertTrue(VRC.iq_stValve.pv_xOPN_SW,'VRC open_SW is set to FALSE');
AssertTrue(VRC.iq_stValve.q_xOPN_DO,'VRC open DO is set to FALSE');
TEST_FINISHED_NAMED('VRC_Interlock_OK');

TEST('VRC_Interlock_Lost');
VRC(i_xExtILK_OK:= FALSE, i_xOverrideMode:= , iq_stValve=> );
AssertFalse(VRC.iq_stValve.pv_xOPN_SW,'VRC open_SW is set to true');
AssertFalse(VRC.iq_stValve.q_xOPN_DO,'VRC open DO is set to true');
TEST_FINISHED_NAMED('VRC_Interlock_Lost');
END_METHOD
Related:

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

// Method Returns True if the Valve is closed, i.e. the open digital output is not active
METHOD PUBLIC M_IsClosed : BOOL
VAR_INPUT
END_VAR
M_IsClosed := NOT This^.q_xOPN_DO;
END_METHOD

// Method Returns True if the Valve is open, i.e. the open digital output is active
METHOD PUBLIC M_IsOpen : BOOL
VAR_INPUT
END_VAR
M_IsOpen := This^.q_xOPN_DO;
END_METHOD

METHOD PUBLIC M_Open : BOOL
VAR_INPUT
    open: bool; //Set to true to open the valvue, false to close
END_VAR
This^.iq_stValve.pv_xOPN_SW := open;
END_METHOD

// Use this Methode to Open or close the valve by setting the OPN_SW to the input value.
METHOD PUBLIC M_Set_OPN_SW : BOOL
VAR_INPUT
    value:BOOL; //True to SET, False to RESET
END_VAR
This^.iq_stValve.pv_xOPN_SW := value;
END_METHOD
Related:

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

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