DUTs

E_ArbDosTestStates

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_ArbDosTestStates :
(
    Init := 0,
    SecondReq := 100,
    WaitForAck := 200,
    WaitFor2ndAck := 300,
    RunAsserts := 8000
);
END_TYPE
Related:

E_BPTMState

TYPE E_BPTMState :
(
    Init := 0,
    NewTarget := 1000,
    RequestBP := 1500,
    WaitForBP := 2500,
    WaitingForTransitionAssertion := 2000,
    WaitingForFinalAssertion := 3000,
    Transitioning := 4000,
    WaitForFinalBP := 5000,
    CleaningUp := 6000,
    Idle := 10000,
    Done := 8000,
    Error := 9000
);
END_TYPE

E_BPTMTestStates

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_BPTMTestStates :
(
    Init := 0,
    WaitingForValidID := 50,
    WaitingForTransitionAssertion := 100,
    StalledAtTransition := 150,
    WaitingForFinalAssertion    := 200,
    WaitingForBeam := 250,
    Transitioning    := 300,
    CleaningUp    := 400,
    AnotherState := 500,
    Retry := 1000,
    Done := 8000,
    Error := 9000
);
END_TYPE

E_MPSBeamClasses

TYPE E_MPSBeamClasses :
(
    Beam_Off    :=  0,
    Kicker_STBY :=  1,
    BC1Hz       :=  2,
    BC10Hz      :=  3,
    Diagnostic  :=  4,
    BC120Hz     :=  5,
    Tuning      :=  6,
    MAP_1       :=  7,
    MAP_5       :=  8,
    MAP_10      :=  9,
    MAP_25      := 10,
    MAP_50      := 11,
    MAP_100     := 12,
    FULL        := 13,
    SPARE1      := 14,
    SPARE2      := 15
);
END_TYPE

E_MPSBeamRates

TYPE E_MPSBeamRates :
(
    MPSRateInvalid            :=  0,
    MPSRate0Hz                :=  1,
    (* Previously used 2 and 3 for single shot and burst *)
    MPSRate1Hz                :=  4,
    MPSRate10Hz               :=  5,
    MPSRate30Hz               :=  6,
    MPSRate60Hz               :=  7,
    MPSRate120Hz              :=  8,
    MPSRateUnknown            :=  9,
    MPSRateSingleShot         := 10,
    MPSRateBurstMode          := 11,
    MPSRateBurstModeNotActive := 12,
    MPSNumberOfBeamRates      := 13,
    MPSRateBurstInvalid       := 14
)UINT;
END_TYPE

K_Apertures

{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE K_Apertures :
(
    SL1K0 := 1,
    SL2K0 := 2
);
END_TYPE

K_Attenuators

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE K_Attenuators :
(
    AT1K0 := 1, // FEE Gas Attenuator
    AT1K4 := 2, // TMO Solid Attenuator in the FEE
    AT1K2 := 3, // RIX Solid Attenuator
    AT2K2 := 4, // RIX Solid Attenuator
    AT1K3 := 5 // TXI Solid Attenuator
);
END_TYPE

K_Stopper

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE K_Stopper :
(
    ST3K4 := 1, // TMO PPS stopper in
    ST1K2 := 2, // RIX PPS stopper in
    MR1K1_IN := 3, // Monochromator focus optic vertical state
    MR1K1_OUT := 4, // Monochromator focus optic vertical state
    MR1K3_IN := 5, // TXI MR1K3 Offset Mirror horizontal state
    MR1K3_OUT := 6, // TXI MR1K3 Offset Mirror horizontal state
    ST4K4 := 7, //TMO ST4K4 Photon Terminator In Limit Switch
    ST1K4 := 8, //TMO Photon Stopper in the FEE
    ST1K3 :=9, //TXI Kline PPS stopper in
    MR2K3_IN := 10, // TXI MR2K3 Offset Mirror state
    MR2K3_OUT := 11, // TXI MR2K3 Offset Mirror horizontal state
    DEFAULT := PMPS_GVL.MAX_VETO_DEVICES // DO NOT USE FOR ANYTHING
);
END_TYPE
Related:

L_Apertures

{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE L_Apertures :
(
    SL1L0 := 1,
    SL2L0 := 2
);
END_TYPE

L_Attenuators

{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE L_Attenuators :
(
    AT1L0 := 1, // FEE Combo Gas/Solid Attenuator
    AT2L0 := 2 // FEE Solid Attenuator
);
END_TYPE

L_Stopper

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE L_Stopper :
(
    ST1L0 := 1, // ST1
    ST1L1 := 2, // TXI PPS Stopper in
    MR1L0_L0 := 3, // MR1L0 Pitch state delivery to L0 line
    MR1L0_TXI := 4, //MR1L0 Pitch state delivery to TXI line
    MR1L1_IN:= 5, //  TXI Offset Mirror horizontal state
    MR1L1_OUT:= 6, // TXI Offset Mirror horizontal state
    DEFAULT := PMPS_GVL.MAX_VETO_DEVICES // DO NOT USE FOR ANYTHING
);
END_TYPE
Related:

PMPS_CODES

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE PMPS_CODES :
(
    FAST_FAULT := 16#1, // Generic fast fault
    PEW_FAULT := 16#7, // Fault occurs when the calculated machine photon energy (K value calculated by undulator gap, and electron energy) falls outside the permitted range.

    // Arbiter codes
    //////////////////////////////////
        ARB_FULL := 16#201, // Arbiter pool is full.

    //BPTM Codes
    //////////////////////////////////
        BadTargetID := 16#300,
        BadTransID := 16#301,
        TransAssrtFail := 16#302,
        FinalAssrtFail := 16#303,
        NoRoomInArb := 16#304
) UINT;
END_TYPE

ST_AttenuatorStatus

TYPE ST_AttenuatorStatus :
STRUCT
    AtTarget : BIT;     //0
    Moving : BIT;       //1
    LocalMode : BIT;    //2  (* means the attenuator ignores PMPS preemptive requests and operates from a local setpoint *)
    PMPSMode : BIT;     //3  (* means the attenuator is only taking setpoints from the pmps *)
    OK : BIT;           //4
    Toggle : BIT;       //5  just here for fun
    Include : BIT;      //6  Consider this device status
END_STRUCT
END_TYPE

ST_BeamParams

TYPE ST_BeamParams :
    STRUCT
        (*  Requested pre-optic attenuation %  *)
        {attribute 'pytmc' := 'pv: Transmission
            io: i
            field: HOPR 1;
            field: LOPR 0;
            field: PREC 2;
        '}
        nTran : REAL := 0;
        (* Pulse-rate *)
        {attribute 'pytmc' := 'pv: Rate
            io: i
            field: EGU Hz
        '}
        nRate : UDINT := 0;
        (* Photon energy ranges *)
        {attribute 'pytmc' := 'pv: eVRanges
            io: i
            field: EGU eV'}
        {attribute 'displaymode' := 'binary'}
        neVRange : DWORD := 0;
                 (* Photon energy  *)
        {attribute 'pytmc' := 'pv: PhotonEnergy
            io: i
            field: EGU eV'}
        neV : REAL := 0;
        (* Beamclass ranges *)
         {attribute 'pytmc' := 'pv: BeamClassRanges
            io: i'}
        {attribute 'displaymode' := 'binary'}
        nBCRange : WORD := 0;
        (* Beamclass *)
        {attribute 'pytmc' := 'pv: BeamClass
            io: i'}
        nBeamClass : USINT :=0;
        (* Machine Mode *)
        {attribute 'pytmc' := 'pv: MachineMode
            io: i'}
        nMachineMode: USINT:=3;
        (* Beamline attenuators *)
        {attribute 'pytmc' := 'pv: AuxAtt
        io: i'}
        astAttenuators : ARRAY [1..PMPS_GVL.AUX_ATTENUATORS] OF ST_PMPS_Attenuator;

        (* Stoppers *)
        {attribute 'pytmc' := 'pv: Veto
        io: i'}
        aVetoDevices : ARRAY [1..PMPS_GVL.MAX_VETO_DEVICES] OF BOOL;

        (* Apertures *)
        {attribute 'pytmc' := 'pv: Apt
        io: i'}
        astApertures : ARRAY [1..PMPS_GVL.MAX_APERTURES] OF ST_PMPS_Aperture := [(Width:=1E3, Height:=1E3), (Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3)];

        // Toggle for watchdog
        xValidToggle : BOOL;
        // Beam parameter set is valid (if readback), or acknowledged (if request)
        {attribute 'pytmc' := 'pv: Valid
        io: i'}
        xValid : BOOL;
        // Cohort index. Identifies which cohort this BP set was included in arbitration
        {attribute 'pytmc' := 'pv: Cohort
        io: i
            field: DESC Cohort inc on each arb cycle
        '}
        nCohortInt : UDINT;
    END_STRUCT
END_TYPE
Related:

ST_BP_ArbInternal

TYPE ST_BP_ArbInternal EXTENDS ST_BeamParams :
STRUCT
    {attribute 'pytmc' := 'pv: ID
        io: i
    '}
    nId : DWORD;
    {attribute 'pytmc' := 'pv: Live
        io: i
    '}
    LiveInTable : BOOL;
    {attribute 'pytmc' := 'pv: Device
        io: i
    '}
    sDevName: STRING;
END_STRUCT
END_TYPE
Related:

ST_DbStateParams

TYPE ST_DbStateParams :
     // Defines The parameters that are loaded from the json db for every state name
STRUCT
      // PMPS database lookup name for this state
    {attribute 'pytmc' := '
        pv: PMPS_STATE
        io: i
        field: DESC PMPS Database Lookup Key
    '}
    sPmpsState: STRING;

    // Beam parameters associated with this state
    {attribute 'pytmc' := '
        pv: BP
        io: i
    '}
    stBeamParams: ST_BeamParams := PMPS_GVL.cst0RateBeam;

    // Set to TRUE once the PMPS library has loaded a valid state from the database
    {attribute 'pytmc' := '
        pv: PMPS_LOADED
        io: i
        field: DESC TRUE if PMPS loaded parameters from the database.
    '}
    bBeamParamsLoaded: BOOL;

     // Reactive parameters associated with this state
    {attribute 'pytmc' := '
        pv: ReParams
        io: i
    '}
    stReactiveParams : ST_ReactiveParams;

    // Transition ID associated with this state
    {attribute 'pytmc' := '
        pv: PMPS_ID
        io: i
        field: DESC Assertion Request ID
    '}
    nRequestAssertionID: UDINT;
END_STRUCT
END_TYPE
Related:

ST_Device

TYPE ST_Device :
STRUCT
    //IO
    stAxis  :       AXIS_REF;
    //The following limit switches must be TRUE or high (ie. NC) to enable that direction of motion
    //The EPICS display should show an inverted indicator to communicate end of travel reached
    i_xLoLim        AT %I*  :       BOOL; //Wire normally-closed
    i_xHiLim        AT %I*  :       BOOL; //Wire normally-closed

    //Acuator Parameters
    lrUpperPositionLimit    :       LREAL := 10000000000; //Use in conjuntion with limit switches or set to a huge number to ignore
    lrLowerPositionLimit    :       LREAL := -10000000000; //Use in conjuntion with limit switches or set to a huge number to ignore

    xEnable :       BOOL := TRUE;

    //Supervisory System

    //MPS Override
    xOverrideMPSLimits      : BOOL := FALSE; //Set true to move beyond MPS limits. Note this will trip beam off.

    //Status and Faults
    xClearFaults    :       BOOL;   //Rising edge tries to clear faults of all kinds

    xGovernorFault  :       BOOL;   //Indicates governor is faulted
    xGovernorOverridden     :       BOOL; //Indicates governor is in override mode
    xSMFault                :       BOOL;   //Indicates component state machine is faulted

    //Device States
    StateTable              :       FB_LinearDeviceStateTable;

    stTransitionState       :       ST_DeviceState; //The transition state might have non-zero beam parameters
    nSafeState                      :       UDINT; //State where the device is considered safe
    nTransitionState        :       UDINT;

    nRequestedState :       UDINT; //EPICS control, cleaned (checked against array limits) and deposited in nTargetState
    nTargetState    :       UDINT; //The requested state should be deposited here after cleaning
    nActiveState    :       UDINT; //The last successfully reached state


END_STRUCT
END_TYPE
Related:

ST_DeviceState

TYPE ST_DeviceState :
STRUCT

    //State name
    sStateName      :       STRING;
    nStateRef       :       UDINT; //Primary key of device state database

    //////////////////
    // This stuff may belong in a sub-class or something
    //Position
    rPosition       :       REAL := 0; //Center of the state
    //Tolerance
    rTolerance :    REAL := 0; //+/- amount
    //////////////////
    //Required beam parameters
    stReqBeamParam  :       ST_BeamParams;

END_STRUCT
END_TYPE
Related:

ST_DeviceStateExt

//Use when working with states in code
//Extended with a valid boolean to indicate the structure was loaded
//properly
TYPE ST_DeviceStateExt EXTENDS ST_DeviceState :
STRUCT
    xValid  :       BOOL := FALSE;
END_STRUCT
END_TYPE
Related:

ST_DeviceStateTable

TYPE ST_DeviceStateTable :
STRUCT
    iDeviceID:DINT;
    iStateID:DINT;
    rTargetPosition: REAL;
    rTolerance: REAL;
    rTransmission: REAL;
    iBeamClass : DINT;
    iPulseRate: DINT;
    rPerPulseEnergy:REAL;
    rUpper_eV: REAL;
    rLower_eV: REAL;
    rPhotonEnergy: REAL;
END_STRUCT
END_TYPE

ST_FF

TYPE ST_FF :
STRUCT
    {attribute 'pytmc' := '
        pv: Info
     '}
    Info : ST_FFInfo;

    {attribute 'pytmc' := '
        pv: Ovrd
     '}
    Ovrd : ST_FFOverride;

    {attribute 'pytmc' := '
        pv: OK
        io: i
     '}
    OK   :   BOOL; // Fault logic state

    FaultAck : BOOL; // Set when faulted, reset by logger.
    ClearAck : BOOL;

    {attribute 'pytmc' := '
        pv: BeamPermitted
        io: i
     '}
    BeamPermitted   :   BOOL; // Result of reset, veto, and fault logic, true beam off boolean

    {attribute 'pytmc' := '
        pv: Reset
        io: o
     '}
    Reset   :   BOOL;

    bsFF    :    RS;
    rtReset    :    R_TRIG;
    ftCountFault : F_TRIG;

END_STRUCT
END_TYPE
Related:

ST_FFInfo

// These elements should be set at init and never changed.

TYPE ST_FFInfo :
STRUCT
    {attribute 'pytmc' := '
        pv: Path
        io: i
     '}
    sPath   :    T_MaxString; // Full PLC path to FF object
    {attribute 'pytmc' := '
        pv: Desc
        io: i
     '}
    Desc    :   T_MaxString; // Set at instantiation to a helpful description of the fast fault purpose
    {attribute 'pytmc' := '
        pv: DevName
        io: i
     '}
    DevName :   T_MaxString; // Component name, used in diagnostic to help narrow down where beam faults are coming from
    {attribute 'pytmc' := '
        pv: TypeCode
        io: i
     '}
    TypeCode    :   UINT; // Set at instantiation to fault class code

///////////////////////////////////////////
// Warning, this variable is effectively a VETO of the fast fault if set FALSE
// Do not alter the state of this variable unless you really know what you're doing.
///////////////////////////////////////////
    {attribute 'pytmc' := '
        pv: InUse
        io: i
     '}
    InUse   :   BOOL := FALSE; // If this FF is currently in-use
///////////////////////////////////////////


    AutoReset   :   BOOL; // Automatically clear fast fault (latching vs non-latching)
    Vetoable  : BOOL := TRUE; // Can this fast fault be masked by the veto device input?

      {attribute 'pytmc' := '
        pv: InfoString
        io: i
     '}
    InfoString : STRING;
END_STRUCT
END_TYPE

ST_FFOverride

TYPE ST_FFOverride :
STRUCT
    {attribute 'pytmc' := '
        pv: Duration
        io: o
     '}
    Duration : DINT; // DINT to be compatible with EPICS

    {attribute 'pytmc' := '
        pv: Expiration
        io: o
     '}
    Expiration : DINT; // DINT to be compatible with EPICS

    {attribute 'pytmc' := '
        pv: StartDT
        io: o
     '}
    StartDT : DINT; // DINT to be compatible with EPICS

    {attribute 'pytmc' := '
        pv: Activate
        io: o
     '}
    Activate : BOOL;

    {attribute 'pytmc' := '
        pv: Deactivate
        io: o
     '}
    Deactivate : BOOL;

    {attribute 'pytmc' := '
        pv: ElapsedTime
        io: i
     '}
    ElapsedTime : DINT; // DINT to be compatible with EPICS

    {attribute 'pytmc' := '
        pv: RemainingTime
        io: i
     '}
    RemainingTime : DINT; // DINT to be compatible with EPICS

    {attribute 'pytmc' := '
        pv: Active
        io: i
     '}
    Active: BOOL;

    Timer    :   TP;
    OvrdActLogAck : BOOL;
    OvrdExpLogAck : BOOL;

    tOvrdActivate : R_TRIG;
    tOvrdExpiring  : F_TRIG;
END_STRUCT
END_TYPE

ST_PMPS_Aperture

TYPE ST_PMPS_Aperture EXTENDS ST_PMPS_Aperture_IO :
STRUCT
    (*
    {attribute 'pytmc' := 'pv: Width
            io: i
            field: EGU mm'}
    Width : REAL; // distance between horizontal slits (x)
    {attribute 'pytmc' := 'pv: Height
            io: i
            field: EGU mm'}
    Height : REAL; // distance between vertical slits (y)

    {attribute 'pytmc' := 'pv: OK
        io: i'}
    xOK : BOOL; // status of aperture, false if error or in motion
    *)
END_STRUCT
END_TYPE

ST_PMPS_Attenuator

TYPE ST_PMPS_Attenuator EXTENDS ST_PMPS_Attenuator_IO :
STRUCT
    (*
        {attribute 'pytmc' := 'pv: Att
            io: i
            field: EGU %
        '}
        nTran : UINT;
        {attribute 'pytmc' := 'pv: OK
            io: i
        '}
        xAttOK : UINT; // true = no errors and attenuator is stable
        *)
END_STRUCT
END_TYPE

ST_ReactiveParams

TYPE ST_ReactiveParams :
STRUCT
    {attribute 'pytmc' := 'pv: TempSP
            io: i
            field: PREC 2;
            field: EGU "C";
        '}
    nTempSP: REAL;
    {attribute 'pytmc' := 'pv: PressSP
            io: i
            field: PREC 2;
            field: EGU "TORR";
        '}
    nPressSP: REAL;
END_STRUCT
END_TYPE

T_HashTableEntry

TYPE T_HashTableEntry :
STRUCT
    {attribute 'pytmc' := '
        pv: Key
        io: i
     '}
    key : DWORD := 0;
    value : PVOID := 0;
END_STRUCT
END_TYPE

GVLs

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_PMPS : ST_LibVersion := (iMajor := 3, iMinor := 3, iBuild := 0, iRevision := 0, nFlags := 1, sVersion := '3.3.0');
END_VAR

PMPS_GVL

{attribute 'qualified_only'}
VAR_GLOBAL
    {attribute 'pytmc' := '
        pv: @(PREFIX)RequestedBP
        io: i
        archive: 1Hz monitor
    '}
    stRequestedBeamParameters    :    ST_BeamParams; //Summarized request for the line, as recognized by the line arbiter PLC
    {attribute 'pytmc' := '
        pv: @(PREFIX)CurrentBP
        io: i
        archive: 1Hz monitor
    '}
    stCurrentBeamParameters    :    ST_BeamParams; //Currently active BP set, broadcast by the line arbiter PLC

    {attribute 'pytmc' := '
        pv: @(PREFIX)eVRangeCnst
        io: i
        archive: 1Hz monitor
        field: DESC Active eV Range constants
        field: EGU eV
    '}
    g_areVBoundaries    :    ARRAY [0..g_cBoundaries]    OF    REAL;



    PERange : PE_Ranges; //Included to place the ev ranges properly
END_VAR
VAR_GLOBAL RETAIN
    // Statistics
    {attribute 'pytmc' := '
        pv: @(PREFIX)SuccessfulPreemptions
        io: i
    '}
    SuccessfulPreemption : UDINT; // Any time BPTM applies a new BP request which is confirmed
    {attribute 'pytmc' := '
        pv: @(PREFIX)AccumulatedFastFaults
        io: i
    '}
    AccumulatedFF : UDINT; // Any time a FF occurs
    BP_jsonDoc              : SJsonValue;
END_VAR
VAR_GLOBAL CONSTANT

    EXCLUDED_ASSERTION_ID    :    UDINT    := 16#FFFFFFFF; //An assertion ID that should always return "not found" in the assertion pool
    VISIBLE_TEST_VELOCITY    :    LREAL    := 10;
    FAST_TEST_VELOCITY        :    LREAL    := 100;
    MAX_DEVICE_STATES        :    UDINT    := 300;

    TRANS_SCALING_FACTOR : REAL := REAL#1.0; // Scaling factor for fixed-point transmission

    AUX_ATTENUATORS : UINT := 16; // Maximum # of attenuators in the PMPS
    MAX_VETO_DEVICES   :   UINT := 16;
    stAttenuators : ST_PMPS_Attenuator :=(nTran:=1,xAttOK:=1);
    {attribute 'pytmc' := '
        pv: @(PREFIX)FullBeamCnst
        io: i
        archive: 1Hz monitor
        field: DESC Full beam constant
    '}
    cstFullBeam    :    ST_BeamParams := (
        nTran := 1,
        neVRange :=  2#1111_1111_1111_1111_1111_1111_1111_1111,
        nRate    := UDINT#1000000,
        nBCRange := 2#0111_1111_1111_1111,
        astApertures := [(Width:=1E3, Height:=1E3), (Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3)],
        astAttenuators :=  [PMPS_GVL.AUX_ATTENUATORS(stAttenuators)]

    );
    {attribute 'pytmc' := '
        pv: @(PREFIX)0RateBeamCnst
        io: i
        archive: 1Hz monitor
        field: DESC 0-rate beam constant
    '}
    cst0RateBeam    :    ST_BeamParams := ( // Use for transition requests
        nTran := 1,
        neVRange :=  2#1111_1111_1111_1111_1111_1111_1111_1111,
        nRate    := 0,
        nBCRange := 2#0000_0000_0000_0000,
        astApertures := [(Width:=1E3, Height:=1E3), (Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3)],
        astAttenuators :=  [PMPS_GVL.AUX_ATTENUATORS(stAttenuators)]
    );
   (*  {attribute 'pytmc' := '
        pv: @(PREFIX)SafeBeamCnst
        io: i
        archive: 1Hz monitor
        field: DESC Safe beam constant
    '}
   cstSafeBeam    :    ST_BeamParams := (
        nTran := 0,
        neVRange := 0,
        nRate    := 0
    );*)

    cnMaxStateArrayLen : INT := 20;


    MAX_APERTURES : UINT := 4; // Maximum # of power slits in the PMPS
    {warning disable C0228}
    DUMMY_AUX_ATT_ARRAY : ARRAY [1..PMPS_GVL.AUX_ATTENUATORS] OF ST_PMPS_Attenuator;
    {warning restore C0228}



    g_cBoundaries    :    INT    := 31;


//////////////////////////
// L Undulator constants
//////////////////////////

///////////////////////////////////////

    {attribute 'pytmc' := '
        pv: @(PREFIX)eVRangeHyst
        io: i
        archive: 1Hz monitor
        field: DESC eV Range hystersis
        field: EGU eV
    '}
    reVHyst: REAL := 5; //


    {attribute 'pytmc' := '
        pv: @(PREFIX)L:eVRangeCnst
        io: i
        archive: 1Hz monitor
        field: DESC eV Range constants
        field: EGU eV
    '}
    g_areVBoundariesL    :    ARRAY [0..g_cBoundaries]    OF    REAL    := [
    1.0E3,
    1.7E3,
    2.1E3,
    2.5E3,
    3.8E3,
    4.0E3,
    5.0E3,
    7.0E3,
    7.5E3,
    7.7E3,
    8.9E3,
    10.0E3,
    11.1E3,
    12.0E3,
    13.0E3,
    13.5E3,
    14.0E3,
    16.9E3,
    18.0E3,
    20.0E3,
    22.0E3,
    24.0E3,
    25.0E3,
    25.5E3,
    26.0E3,
    27.0E3,
    28.0E3,
    28.5E3,
    29.0E3,
    30.0E3,
    60.0E3,
    90.0E3
    ];


    {attribute 'pytmc' := '
        pv: @(PREFIX)K:eVRangeCnst
        io: i
        archive: 1Hz monitor
        field: DESC eV Range constants
        field: EGU eV
    '}
    g_areVBoundariesK    :    ARRAY [0..g_cBoundaries]    OF    REAL    := [
    100,
    250,
    270,
    350,
    400,
    450,
    480,
    530,
    680,
    730,
    850,
    1.10E3,
    1.15E3,
    1.25E3,
    1.45E3,
    1.50E3,
    1.55E3,
    1.65E3,
    1.70E3,
    1.75E3,
    1.82E3,
    1.85E3,
    2.00E3,
    2.20E3,
    2.50E3,
    2.80E3,
    3.00E3,
    3.15E3,
    3.50E3,
    4.00E3,
    5.30E3,
    7.00E3
    ];


END_VAR
Related:

PMPS_PARAM

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    MAX_FAST_FAULTS :       UINT := 50; // Max fast faults for an FFO
    MAX_ASSERTIONS    :    UDINT := 20; //Maximum number of BP requests in the arbiter
    TRANS_MARGIN : REAL := REAL#0.05; // Allowed % margin above requested transmission level in SafeBPCompare (0.0500 = 5deci% default). Note: change this value if scaling factor changes.
END_VAR

PMPS_TOOLS

{attribute 'qualified_only'}
VAR_GLOBAL
    fbJson : FB_JsonSaxWriter;
END_VAR

POUs

APT_TO_IO

FUNCTION APT_TO_IO : ST_PMPS_Aperture_IO
VAR_INPUT
    Apt : ST_PMPS_Aperture;
END_VAR
VAR
END_VAR
APT_TO_IO.Height := Apt.Height;
APT_TO_IO.Width := Apt.Width;
APT_TO_IO.xOK := Apt.xOK;

END_FUNCTION
Related:

ATT_TO_IO

FUNCTION ATT_TO_IO : ST_PMPS_Attenuator_IO
VAR_INPUT
    Att : ST_PMPS_Attenuator;
END_VAR
VAR
END_VAR
ATT_TO_IO.nTran := Att.nTran;
ATT_TO_IO.xAttOK := Att.xAttOK;

END_FUNCTION
Related:

BeamParameterTransitionManager

(*
Implements the procedure for safely transitioning between device states.

NOTE:
The BPTM will throw an error if the arbiter does not have enough space for the transition and new final assertion.

 *)
{attribute 'no_check'}
FUNCTION_BLOCK BeamParameterTransitionManager
VAR_IN_OUT

    fbArbiter    :    FB_Arbiter; //Connect to local arbiter

END_VAR
VAR_INPUT
    i_sDeviceName   :       STRING:='Device'; // Name of the device requesting the transition

    i_TransitionAssertionID    :    UDINT := 0; // Must not be 0 or EXCLUDED_ID
    i_stTransitionAssertion : ST_BeamParams := PMPS_GVL.cst0RateBeam; //Assertion required during transition (always safer than anything inbetween)

    i_nRequestedAssertionID    :    UDINT := 0; // Must not be 0 or EXCLUDED_ID
    i_stRequestedAssertion    :    ST_BeamParams := ( nTran := 0, neVRange := 0, nRate  := 0, nBCRange :=0);// PMPS_GVL.cstSafeBeam; //Requested assertion, change whenever

    i_xMoving    :    BOOL := FALSE; //Provide rising edge when device begins moving <remove>
    i_xDoneMoving    :    BOOL := FALSE; //Provide rising edge when device is done with a move

    stCurrentBeamParameters    :    ST_BeamParams := PMPS_GVL.cstFullBeam; //Connect to current beam parameters

    // Rising edge to cycle back through the BPTM process. Use if something in the process timed out or failed. This will interrupt a current process
    bRetry : BOOL := FALSE;
END_VAR
VAR_OUTPUT

    q_xTransitionAuthorized    :    BOOL := FALSE; //Rising edge indicating the device is safe to move, use as input to move execute (which requires a rising edge)

    bError : BOOL; // Set if some issue occurs within the bptm
    nErrId : UINT; // Set to non-zero to help understand the error.

    bDone : BOOL;
    bBusy : BOOL;

END_VAR

VAR
    nTargetAssertionID    :    UDINT := 0;
    stTargetAssertion    :    ST_BeamParams; // Target assertion
    nCurrentAssertionID    :    UDINT := 0;  // ID of last set state (zero until a state is reached)

    xNewBP    :    BOOL;
    xTranBP    :    BOOL;
    xFinalBPInArb : BOOL;
    xFinalBP    :    BOOL;

    eBPTMState : E_BPTMState := Init;
    ePrevState : E_BPTMState := Init;

    xEntry : BOOL;
    rTransition : R_TRIG;
    xNewTarget : BOOL;

    bTransAssertionFailed : BOOL;
    bFinalAssertionFailed : BOOL;

    LogStrBuffer : ARRAY [0..LogBuffSize] OF STRING;
    LogBuffIdx : FB_Index := (LowerLimit:=0, UpperLimit:=LogBuffSize);

    nAssrtAttempt : INT; // Number of times we have tried asserting a BP set

    rtRetry : R_TRIG;

    rtError : R_TRIG;

    ffTimeout : FB_FastFault := (
        i_Desc := 'Preemptive requests timed out in BPTM',
        i_TypeCode := 16#A,
        i_xAutoReset := FALSE);

    rtDoneMoving : R_TRIG;
    bLatchDoneMoving : BOOL;

    bFirstMove : BOOL := TRUE;

END_VAR
VAR CONSTANT
    LogBuffSize : INT := 40;
    cMaxAttempts : INT := 3;
    (* The thought here is, a BPTM needs at most 2 arbiter slots to complete a transition.
    If we're at capacity, it means some BPTM before this one has begun a transition,
    and will require at least one more arbiter spot to complete.
    *)
    cReqArbCapacity : UDINT := 2;
END_VAR
// Logic for retry button
rtRetry(CLK:=bRetry);
bRetry := FALSE;

// Logic for catching Move Done rising edge
rtDoneMoving(CLK:=i_xDoneMoving);
bLatchDoneMoving S= rtDoneMoving.Q;

// Logic for interrupting BPTM and changing requests
IF nTargetAssertionID <> i_nRequestedAssertionID OR rtRetry.Q THEN

    bError := FALSE;
    nErrID := 0;

    bDone := FALSE;
    bBusy := TRUE;

    // Execute exit actions before transition back to NewTarget
    CASE eBPTMState OF
        Transitioning:
            DeauthorizeTransition();
        //CleaningUp:
    END_CASE

    eBPTMState := E_BPTMState.NewTarget;
    xEntry := eBPTMState <> ePrevState;

END_IF

IF xEntry THEN nAssrtAttempt := 0; END_IF

rtError(CLK:=bError);

IF rtError.Q THEN
    eBPTMState := E_BPTMState.Error;
END_IF

// States
CASE eBPTMState OF
    NewTarget:
        IF xEntry THEN
            NewTarget_ENTRY();
        ELSIF xNewBP THEN
            eBPTMState := E_BPTMState.RequestBP;
        ELSE
            SetNewTarget();
        END_IF

    RequestBP:
        IF PMPS_PARAM.MAX_ASSERTIONS - fbArbiter.nEntryCount >= cReqArbCapacity THEN
            AssertTransitionBP();
            AssertFinalBP();
            eBPTMState := E_BPTMState.WaitForBP;
        ELSE
            LogActions('Arbiter at capacity, leaving space for other BPTM to finish.');
            nErrId := PMPS_CODES.NoRoomInArb;
            bError := TRUE;
        END_IF

    WaitForBP:
        WaitingForTransitionAssertion_DO();
        WaitingForFinalAssertion_DO();
        IF xTranBP AND xFinalBPInArb THEN eBPTMState := E_BPTMState.Transitioning; END_IF

    Transitioning:
        IF xEntry THEN
            AuthorizeTransition();
        ELSIF bLatchDoneMoving OR (bFirstMove AND i_xDoneMoving) THEN
            DeauthorizeTransition();
            eBPTMState := E_BPTMState.WaitForFinalBP;
        END_IF

    WaitForFinalBP:
        xFinalBP := F_SafeBPCompare(stCurrentBeamParameters, stTargetAssertion);
        IF xFinalBP THEN
            eBPTMState := E_BPTMState.CleaningUp;
        END_IF

    CleaningUp:
        IF xEntry THEN
            RemoveTransitionAssertion();
        ELSE
            eBPTMState := E_BPTMState.Done;
        END_IF

    Done:
        bDone := TRUE;
        bFirstMove := FALSE;
        LogActions('Returning to idle');
        eBPTMState := E_BPTMState.Idle;

    Error:
        bError := TRUE;
        eBPTMState := E_BPTMState.Idle;
END_CASE

xEntry := ePrevState <> eBPTMState;
ePrevState := eBPTMState;

bDone R= bError;
bBusy R= bError OR bDone;

END_FUNCTION_BLOCK

ACTION AssertFinalBP:
//q_stFinalAssertion := stTargetAssertion;

//Clearing previous target parameters
(* This is considered safe at this step as the transition
state beam parameters should always be safer than the
current or target state beam parameters
*)
IF nCurrentAssertionID <> 0 THEN
    LogActions(CONCAT('Removing previous request: ', DWORD_TO_HEXSTR(nCurrentAssertionID, 4, false)) );
    fbArbiter.RemoveRequest( nCurrentAssertionID );
END_IF

LogActions(CONCAT('Asserting target req.: ', DWORD_TO_HEXSTR(nTargetAssertionID, 4, false)) );

//Asserting target parameters
bFinalAssertionFailed := NOT fbArbiter.AddRequest(
    nTargetAssertionID,
    stTargetAssertion,
    i_sDeviceName
);

IF bFinalAssertionFailed THEN
    LogActions('Assert final failed. ');
    nAssrtAttempt := nAssrtAttempt + 1;
    IF nAssrtAttempt > cMaxAttempts THEN
        bError := TRUE;
        nErrID := PMPS_CODES.FinalAssrtFail;
    END_IF
ELSE
    // Remembering final assertionID for removal later
    nCurrentAssertionID := nTargetAssertionID;
END_IF
END_ACTION

ACTION AssertTransitionBP:
LogActions( CONCAT('Removing transition id: ', DWORD_TO_STRING(i_TransitionAssertionID)) );
fbArbiter.RemoveRequest(i_TransitionAssertionID);

LogActions( CONCAT('Requesting transition id: ', DWORD_TO_STRING(i_TransitionAssertionID)) );
bTransAssertionFailed := NOT fbArbiter.AddRequest(
    i_TransitionAssertionID,
    i_stTransitionAssertion,
    i_sDeviceName
);

IF bTransAssertionFailed THEN
    LogActions('Assert trans. failed. ');
    nAssrtAttempt := nAssrtAttempt + 1;
    IF nAssrtAttempt > cMaxAttempts THEN
        bError := TRUE;
        nErrID := PMPS_CODES.TransAssrtFail;
    END_IF
END_IF
END_ACTION

ACTION AuthorizeTransition:
q_xTransitionAuthorized := TRUE;

bLatchDoneMoving R= q_xTransitionAuthorized;

LogActions('Authorizing transition');
END_ACTION

ACTION DeauthorizeTransition:
q_xTransitionAuthorized := FALSE;
LogActions('Deauthorizing transition');
END_ACTION

ACTION NewTarget_ENTRY:
xNewBP := False;
END_ACTION

ACTION RemoveTransitionAssertion:
//q_stTransitionAssertion := PMPS_GVL.cstFullBeam;

//Removing transition assertion
fbArbiter.RemoveRequest( i_TransitionAssertionID );

LogActions('Removing transition req');

//TODO add something to verify removal
END_ACTION

ACTION RequestBP_DO:
// Request BP atomically so we know we'll finish the BPTM cycle
IF fbArbiter.nEntryCount < cReqArbCapacity THEN

    LogActions( CONCAT('Removing transition id: ', DWORD_TO_STRING(i_TransitionAssertionID)) );
    fbArbiter.RemoveRequest(i_TransitionAssertionID);

    LogActions( CONCAT('Requesting transition id: ', DWORD_TO_STRING(i_TransitionAssertionID)) );
    bTransAssertionFailed := NOT fbArbiter.AddRequest(
        i_TransitionAssertionID,
        i_stTransitionAssertion,
        i_sDeviceName
    );

    IF bTransAssertionFailed THEN
        LogActions('Assert trans. failed. ');
        nAssrtAttempt := nAssrtAttempt + 1;
        IF nAssrtAttempt > cMaxAttempts THEN
            bError := TRUE;
            nErrID := PMPS_CODES.TransAssrtFail;
        END_IF
    END_IF



ELSE
    LogActions('Arbiter at capacity, leaving space for other BPTM to finish.');
END_IF
END_ACTION

ACTION SetNewTarget:
IF F_ValidReqID(i_nRequestedAssertionID) AND
    F_ValidReqID(i_TransitionAssertionID) THEN

    stTargetAssertion := i_stRequestedAssertion;
    nTargetAssertionID := i_nRequestedAssertionID;

    LogActions('New target set');

    xNewBP := TRUE;

ELSE
    IF NOT F_ValidReqID(i_nRequestedAssertionID) THEN

        nErrID := PMPS_CODES.BadTargetID;
    ELSIF NOT F_ValidReqID(i_TransitionAssertionID) THEN
        nErrID := PMPS_CODES.BadTransID;
    END_IF
    LogActions('Error in set new target');
    bError := TRUE;
END_IF
END_ACTION

ACTION WaitingForFinalAssertion_DO:
//Final Assertion Verification

xFinalBPInArb := fbArbiter.CheckRequest( i_nRequestedAssertionID);;
END_ACTION

ACTION WaitingForFinalAssertion_EXIT:
//Set this bool false so we can get a rising edge on the next try
xFinalBP := FALSE;
END_ACTION

ACTION WaitingForTransitionAssertion_DO:
//Transition Assertion Verification
xTranBP    := F_SafeBPCompare0Rate(stCurrentBeamParameters, i_stTransitionAssertion) AND
    fbArbiter.CheckRequest(i_TransitionAssertionID);
END_ACTION

ACTION WaitingForTransitionAssertion_EXIT:
xTranBP := FALSE;
END_ACTION

{attribute 'no_check'}
METHOD LogActions : BOOL
VAR_INPUT
    LogStr : STRING;
END_VAR
LogStrBuffer[LogBuffIdx.IncVal()] := LogStr;
END_METHOD
Related:

BP_TO_IO

{attribute 'no_check'}
FUNCTION BP_TO_IO : ST_BeamParams_IO
VAR_INPUT
    BP : ST_BeamParams;
END_VAR
VAR
    idx : UINT := 0;
END_VAR
FOR idx := 1 TO PMPS_GVL.AUX_ATTENUATORS DO
    BP_TO_IO.astAttenuators[idx] := ATT_TO_IO(BP.astAttenuators[idx]);
END_FOR

FOR idx := 1 TO PMPS_GVL.MAX_APERTURES DO
    BP_TO_IO.astApertures[idx] := APT_TO_IO(BP.astApertures[idx]);
END_FOR

BP_TO_IO.aVetoDevices := BP.aVetoDevices;
BP_TO_IO.nTran := BP.nTran;
BP_TO_IO.nCohortInt := ULINT_TO_UDINT(BP.nCohortInt);
BP_TO_IO.neVRange := BP.neVRange;
BP_TO_IO.neV := BP.neV;
BP_TO_IO.nBCRange := BP.nBCRange;
BP_TO_IO.nBeamClass := BP.nBeamClass;
BP_TO_IO.nMachineMode := BP.nMachineMode;
BP_TO_IO.nRate := BP.nRate;
BP_TO_IO.xValid := BP.xValid;
BP_TO_IO.xValidToggle := BP.xValidToggle;

END_FUNCTION
Related:

CheckBounds

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckBounds : DINT
VAR_INPUT
    index, lower, upper: DINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF  index < lower THEN
    CheckBounds := lower;
ELSIF  index > upper THEN
    CheckBounds := upper;
ELSE
    CheckBounds := index;
END_IF
{flow}

END_FUNCTION

CheckDivDInt

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivDInt : DINT
VAR_INPUT
    divisor:DINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
    CheckDivDInt:=1;
ELSE
    CheckDivDInt:=divisor;
END_IF;
{flow}

END_FUNCTION

CheckDivLInt

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivLInt : LINT
VAR_INPUT
    divisor:LINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
    CheckDivLInt:=1;
ELSE
    CheckDivLInt:=divisor;
END_IF;
{flow}

END_FUNCTION

CheckDivLReal

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivLReal : LREAL
VAR_INPUT
    divisor:LREAL;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
    CheckDivLReal:=1;
ELSE
    CheckDivLReal:=divisor;
END_IF;
{flow}

END_FUNCTION

CheckDivReal

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivReal : REAL
VAR_INPUT
    divisor:REAL;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
    CheckDivReal:=1;
ELSE
    CheckDivReal:=divisor;
END_IF;
{flow}

END_FUNCTION

CheckLRangeSigned

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckLRangeSigned : LINT
VAR_INPUT
    value, lower, upper: LINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF (value < lower) THEN
    CheckLRangeSigned := lower;
ELSIF(value > upper) THEN
    CheckLRangeSigned := upper;
ELSE
    CheckLRangeSigned := value;
END_IF

{flow}

END_FUNCTION

CheckLRangeUnsigned

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckLRangeUnsigned : ULINT
VAR_INPUT
    value, lower, upper: ULINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF (value < lower) THEN
    CheckLRangeUnsigned := lower;
ELSIF(value > upper) THEN
    CheckLRangeUnsigned := upper;
ELSE
    CheckLRangeUnsigned := value;
END_IF
{flow}

END_FUNCTION

CheckPointer

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckPointer : POINTER TO BYTE
VAR_INPUT
    ptToTest : POINTER TO BYTE; // Destination address of the pointer
    iSize : DINT;                   // Size of the type the pointer points to. (e.g: 20 for POINTER TO ARRAY [0..9] OF INT)
    iGran : DINT;                   // Granularity of the pointer access. This is the size of the biggest non-structured data type in the type the pointer points to. (e.g: 2 for POINTER TO ARRAY [0..9] OF INT)
    bWrite: BOOL;                   // Indicates read or write Access. TRUE = write access.
END_VAR
// No standard way of implementation. Fill your own code here
CheckPointer := ptToTest;
{flow}

END_FUNCTION

CheckRangeSigned

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckRangeSigned : DINT
VAR_INPUT
    value, lower, upper: DINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF (value < lower) THEN
    CheckRangeSigned := lower;
ELSIF(value > upper) THEN
    CheckRangeSigned := upper;
ELSE
    CheckRangeSigned := value;
END_IF
{flow}

END_FUNCTION

CheckRangeUnsigned

// Implicitly generated code : DO NOT EDIT
FUNCTION CheckRangeUnsigned : UDINT
VAR_INPUT
    value, lower, upper: UDINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF (value < lower) THEN
    CheckRangeUnsigned := lower;
ELSIF(value > upper) THEN
    CheckRangeUnsigned := upper;
ELSE
    CheckRangeUnsigned := value;
END_IF
{flow}

END_FUNCTION

F_AttenuatorOK

(*
Indicates if the attenuator can be considered safe for a device,
given its status word.

In local mode, the atteunator ignores PMPS requests, following a local
setpoint. In this mode, a device cannot trust that the attenuator will not
increase transmission beyond its own setpoint. Therefore any
indication of a changing transmission should cause a fault.

In PMPS mode, if a device has submitted a preemptive request and received
acknowledgement, then the transmission setpoint will be governed at least
by that device's setpoint. Therefore, the attenuator may be moving without
generating a fault.
*)
FUNCTION F_AttenuatorOK : BOOL
VAR_INPUT
    xStatus : WORD;
END_VAR
VAR
END_VAR
IF xStatus.2 THEN // Local mode
    F_AttenuatorOK := xStatus.0 and NOT xStatus.1;
ELSIF xStatus.3 AND NOT xStatus.2 THEN // PMPS mode
    F_AttenuatorOK := TRUE;
END_IF

F_AttenuatorOK := xStatus.4 AND F_AttenuatorOK;

END_FUNCTION

F_BPWithID

FUNCTION F_BPWithID : ST_BP_ArbInternal
VAR_INPUT
    BP : REFERENCE TO ST_BeamParams;
    ID : DWORD;
END_VAR
VAR
    BpInt : ST_BP_ArbInternal;
END_VAR
MEMCPY(ADR(F_BPWithID), ADR(BP), SIZEOF(BP));
F_BPWithID.nId := ID;

END_FUNCTION
Related:

F_CalculatePhotonEnergy

FUNCTION F_CalculatePhotonEnergy : LREAL
VAR_INPUT
    (* Electron energy in GeV *)
    fElectronEnergy_GeV : LREAL;
    (* Undulator period in mm *)
    fUndulatorPeriod_mm : LREAL;
    (* Unitless undulator K parameter / strength *)
    fUndulatorStrength : LREAL;
END_VAR

VAR
    fDenominator: LREAL;
END_VAR

VAR CONSTANT
    (* GeV [electron rest mass/energy] *)
    m_e : LREAL := 0.0005109989461;
    (* Js [Planck's constant] *)
    h       : LREAL := 6.62607004E-34;
    (* C [electron charge] *)
    e       : LREAL := 1.6021766208E-19;
    (* m/s, speed of light *)
    c       : LREAL := 299792458;

END_VAR
(*

Reference Python implementation

def calculate_photon_energy(electron_energy, period, k):
    '''
    Calculate photon energy, in eV.

    Parameters
    ----------
    electron_energy : float
        Electron energy in GeV

    period : float
        Undulator period, in mm

    k : float
        Undulator K parameter (strength), unitless
    '''
    m_e = 0.0005109989461  # GeV [electron rest mass/energy]
    h = 6.62607004e-34  # Js [Planck's constant]
    e = 1.6021766208e-19  # C [electron charge]
    c = 299792458  # m/s, speed of light
    return ((2. * (electron_energy / m_e) ** 2 * h * c) /
            (e * period * 1e-3 * (1 + k ** 2 / 2.)))

*)
fDenominator := (e * fUndulatorPeriod_mm * 1E-3 * (1.0 + EXPT(fUndulatorStrength, 2.0) / 2.0));
F_CalculatePhotonEnergy := (2.0 * EXPT(fElectronEnergy_GeV / m_e, 2.0) * h * c) / MAX(fDenominator, 4.94065645841247E-324);

END_FUNCTION

F_DeviceState_To_DeviceStateExt

FUNCTION F_DeviceState_To_DeviceStateExt : ST_DeviceStateExt
VAR_INPUT
    stDeviceState    :    ST_DeviceState;
    xValid    :    BOOL;
END_VAR
VAR
END_VAR
F_DeviceState_To_DeviceStateExt.nStateRef := stDeviceState.nStateRef;
F_DeviceState_To_DeviceStateExt.rPosition    :=    stDeviceState.rPosition;
F_DeviceState_To_DeviceStateExt.rTolerance    :=    stDeviceState.rTolerance;
F_DeviceState_To_DeviceStateExt.sStateName    := stDeviceState.sStateName;
F_DeviceState_To_DeviceStateExt.stReqBeamParam    := stDeviceState.stReqBeamParam;
F_DeviceState_To_DeviceStateExt.xValid := xValid;

END_FUNCTION
Related:

F_DifferentBeamParams

(*Compares BeamParam1 to BeamParam2, if any parameters of BeamParam1 are different than BeamParam2
the result will be true. *)
{attribute 'no_check'}
FUNCTION F_DifferentBeamParams : BOOL
VAR_INPUT
    BeamParam1    :    ST_BeamParams;
    BeamParam2    :    ST_BeamParams;
END_VAR
VAR
    xAttOK: BOOL := FALSE;
    xPPmjOK: BOOL := FALSE;
    xEvOK: BOOL := FALSE;
    xRateOK: BOOL := FALSE;
    xaStopper : BOOL := FALSE;
    xaAtt : BOOL := FALSE;
    xaApt : BOOL := FALSE;
    xBCOK : BOOL := FALSE;

    idx : UINT;


END_VAR
xAttOK := BeamParam1.nTran <> BeamParam2.nTran;
xEvOK := BeamParam1.neVRange <> BeamParam2.neVRange;
xRateOK     := BeamParam1.nRate <> BeamParam2.nRate;
xBCOK       := BeamParam1.nBCRange <> BeamParam2.nBCRange;// OR BeamParam1.nBeamClass <> BeamParam2.nBeamClass ; //Is this right?


//ast Attenuators
FOR idx:=1 TO PMPS_GVL.AUX_ATTENUATORS DO
    xaAtt S= (BeamParam1.astAttenuators[idx].nTran <> BeamParam2.astAttenuators[idx].nTran);
END_FOR

// Stoppers
FOR idx:=1 TO PMPS_GVL.MAX_VETO_DEVICES DO
    xaStopper S= (BeamParam1.aVetoDevices[idx] <> BeamParam2.aVetoDevices[idx]);
END_FOR

// ast Apertures
FOR idx:=1 TO PMPS_GVL.MAX_APERTURES DO
    xaApt S= (BeamParam1.astApertures[idx].Height <> BeamParam2.astApertures[idx].Height) OR
            (BeamParam1.astApertures[idx].Width <> BeamParam2.astApertures[idx].Width);
END_FOR


F_DifferentBeamParams := xAttOK OR xPPmjOK OR xEvOK OR xRateOK OR xaStopper OR xaAtt OR xaApt OR xBCOK;

END_FUNCTION
Related:

F_eVExcludeRange

FUNCTION F_eVExcludeRange : DWORD
(*
    Given an lower and upper end of an exclusion range, return the corresponding eV bitmask.

    eVs between fLower and fUpper will be considered unsafe, and eVs outside of this range will be
    considered safe, with the exception of the endpoints and values near the endpoints if they land
    far from an eV boundary.

    Call this in your init cycle to set up your eV bitmasks for more readable code
    that is also more resiliant to eV range definition adjustments.
*)
VAR_INPUT
    fLower: REAL;
    fUpper: REAL;
END_VAR
F_eVExcludeRange := F_eVIncludeRange(0, fLower) OR F_eVIncludeRange(fUpper, PMPS_GVL.g_areVBoundaries[PMPS_GVL.g_cBoundaries]);

END_FUNCTION
Related:

F_eVIncludeRange

FUNCTION F_eVIncludeRange : DWORD
(*
    Given an lower and upper end of an inclusion range, return the corresponding eV bitmask.

    eVs between fLower and fUpper will be considered safe, with the exception of the endpoints and values
    near the endpoints if they land far from an eV boundary.

    Call this in your init cycle to set up your eV bitmasks for more readable code
    that is also more resiliant to eV range definition adjustments.
*)
VAR_INPUT
    fLower: REAL;
    fUpper: REAL;
END_VAR
VAR
    nBitmask: DWORD := 0;
    nIndex: INT;
    nBit: USINT := 0;
    fPrev: REAL := 0;
END_VAR
FOR nIndex := 0 TO PMPS_GVL.g_cBoundaries DO
    IF fLower <= fPrev AND fUpper >= PMPS_GVL.g_areVBoundaries[nIndex] THEN
        nBitmask := nBitmask + SHL(1, nBit);
    END_IF
    fPrev := PMPS_GVL.g_areVBoundaries[nIndex];
    nBit := nBit + 1;
END_FOR

F_eVIncludeRange := nBitmask;

END_FUNCTION
Related:

F_eVRangeCalculator

(*
A. Wallace 2019-8-8

Provides bit range of eV.

LastWord = 0 (Result)
----------------------------------
reV = 300 = (0000_0000_0000_0010)
reV = 301 = (0000_0000_0000_0100)

LastWord = 0000_0000_0000_0010 (Result)
----------------------------------
reV = 300 + <rHyst = (0000_0000_0000_0110)
reV = 300 + >rHyst = (0000_0000_0000_0100)

*)
{attribute 'no_check'}
FUNCTION F_eVRangeCalculator : DWORD // Bit-range of current photon energy
VAR_INPUT
    reV    :    REAL; // Photon energy (keV)
    LastWord    :    DWORD; // Range word of previous cycle
END_VAR
VAR
    nIndex: INT;
    RangeWord: DWORD;
    rPreviousBoundary: REAL := 0;

    MaxEv    :    REAL := PMPS_GVL.g_areVBoundaries[PMPS_GVL.g_cBoundaries];

    Boundaries : ARRAY [0..PMPS_GVL.g_cBoundaries] OF REAL;
END_VAR
{IF defined (L)}
    Boundaries := PMPS_GVL.g_areVBoundariesL;
{ELSIF defined (K)}
    Boundaries := PMPS_GVL.g_areVBoundariesK;
{END_IF}

MaxEv := Boundaries[PMPS_GVL.g_cBoundaries];

IF reV <= 0 OR reV > MaxEv THEN F_eVRangeCalculator := 16#FFFF_FFFF; RETURN; END_IF // Failsafe

FOR nIndex := 0 TO PMPS_GVL.g_cBoundaries DO

    // If previously active then check if it should be cleared
    IF LastWord.0 THEN
        // Hysteresis condition
        IF (reV > MIN(Boundaries[nIndex] + PMPS_GVL.reVHyst, MaxEv) ) OR
        (reV < MAX(rPreviousBoundary - PMPS_GVL.reVHyst, 0) ) THEN
            RangeWord.0 := 0; // If out of range of hys. mark inactive
        ELSE
            RangeWord.0 := 1;
        END_IF
    ELSE // Check if we should mark as active
        IF (reV <= Boundaries[nIndex]) AND
        (reV > rPreviousBoundary) OR
         (nIndex = 0 AND reV = 0 ) THEN
            RangeWord.0 := 1;
        END_IF
    END_IF

    // Catch if the hysteresis is set too large for the range
    IF rPreviousBoundary + PMPS_GVL.reVHyst >= Boundaries[nIndex] THEN
        RangeWord.0 := 1; // Setting the bit true here as a failsafe
        //<TODO> Add major error or warning here
    END_IF

    rPreviousBoundary := Boundaries[nIndex];
    // Shift to next bit
    RangeWord := ROR(RangeWord, 1);
    LastWord := SHR(LastWord, 1);

END_FOR

F_eVRangeCalculator := RangeWord;

END_FUNCTION
Related:

F_eVWithinSpec

//reV must be within permitted range.
{attribute 'no_check'}
FUNCTION F_eVWithinSpec : BOOL
VAR_INPUT
    reV    :    REAL;    //Photon energy to check if within permitted range
    nPermittedRange    :    DWORD; //Permitted range
END_VAR
VAR
    //Holding register for permitted ranges
    nPermittedRangeHolding    :    DWORD;
    //For loop counter
    nIndex    :    INT;
    // Lower boundary for range check
    rPreviousBoundary: REAL := 0;
    Boundaries: ARRAY [0..PMPS_GVL.g_cBoundaries] OF REAL;
END_VAR
(* How this works:
The within range bool is initialized to false.
Load the word representation of ranges into a holding register.
If the bit at position zero of the holding register is true, and the input eV is
below the boundary at the index position of the boundary array, and the previous boundary
(which initializes at 0), then the within range bool is set.
Then, we stash the upper boundary as the lower boundary for the next comparison,
and shift the holding register right, which moves the next bit to the zero position.
*)

{IF defined (L)}
    Boundaries := PMPS_GVL.g_areVBoundariesL;
{ELSIF defined (K)}
    Boundaries := PMPS_GVL.g_areVBoundariesK;
{END_IF}

nPermittedRangeHolding := nPermittedRange;

FOR nIndex := 0 TO PMPS_GVL.g_cBoundaries DO

    F_eVWithinSpec S= nPermittedRangeHolding.0 AND(
        (reV <= Boundaries[nIndex]) AND
        (reV >= rPreviousBoundary) );

    IF F_eVWithinSpec THEN RETURN; END_IF

    rPreviousBoundary := Boundaries[nIndex];
    nPermittedRangeHolding := SHR(nPermittedRangeHolding, 1);
END_FOR

END_FUNCTION
Related:

F_InitTestBP

// Establishes a BP set for testing.
// Specifically this means a BP that has all the status bits set to an OK state
FUNCTION F_InitTestBP : ST_BeamParams
VAR_INPUT
    BP : ST_BeamParams;
END_VAR
VAR
END_VAR


END_FUNCTION
Related:

F_PMPS_JSON

FUNCTION F_PMPS_JSON : STRING
VAR_INPUT
    sDevName : STRING := '';
    sPath : STRING := '';
    nTypeCode : UINT := 0;
END_VAR
VAR

END_VAR
PMPS_TOOLS.fbJson.StartObject();
PMPS_TOOLS.fbJson.AddKey('pmps_typecode');
PMPS_TOOLS.fbJson.AddUdint(nTypeCode);
PMPS_TOOLS.fbJson.AddKey('pmps_path');
PMPS_TOOLS.fbJson.AddString(sPath);
PMPS_TOOLS.fbJson.AddKey('pmps_device_name');
PMPS_TOOLS.fbJson.AddString(sDevName);
PMPS_TOOLS.fbJson.EndObject();
F_PMPS_JSON := PMPS_TOOLS.fbJson.GetDocument();
PMPS_TOOLS.fbJson.ResetDocument();

END_FUNCTION
Related:

F_SafeBPCompare

(*Compares BeamParam1 to BeamParam2: if the parameters of BeamParam1 are more conservative than BeamParam2
the result will be true. *)
// Does not consider status. Status must be evaluated elsewhere.
{attribute 'no_check'}
FUNCTION F_SafeBPCompare : BOOL
VAR_INPUT
    BeamParam1    :    ST_BeamParams; //Must be more conservative than 2
    BeamParam2    :    ST_BeamParams;
END_VAR
VAR
    // Internal Attenuation OK boolean
    xAttOK: BOOL := FALSE;
    // Internal Per-pulse energy OK boolean
    xPPmjOK: BOOL := FALSE;
    // Internal photon energy OK boolean
    xEvOK: BOOL := FALSE;
    // Internal Beam Rate OK boolean
    xRateOK: BOOL;
    //Internal Beam Class Range OK boolean
    xBCOK : BOOL := FALSE;
    idx : UINT;
    xAuxAttOK : BOOL := TRUE;
    xAuxAprtOK : BOOL := TRUE;

    Att1 : REFERENCE TO ST_PMPS_Attenuator;
    Att2 : REFERENCE TO ST_PMPS_Attenuator;

END_VAR
xAttOK := BeamParam1.nTran <= MIN(PMPS_GVL.TRANS_SCALING_FACTOR, (BeamParam2.nTran *(PMPS_GVL.TRANS_SCALING_FACTOR+PMPS_PARAM.TRANS_MARGIN))/PMPS_GVL.TRANS_SCALING_FACTOR );
xEvOK := (BeamParam1.neVRange AND BeamParam2.neVRange) = BeamParam1.neVRange;
xRateOK    := (BeamParam1.nRate <= BeamParam2.nRate) OR (PMPS_GVL.stCurrentBeamParameters.nMachineMode =1); //ignore in SC mode
xBCOK := ((BeamParam1.nBCRange AND BeamParam2.nBCRange) = BeamParam1.nBCRange) OR (PMPS_GVL.stCurrentBeamParameters.nMachineMode =0); //ignore in NC mode;

FOR idx:=1 to PMPS_GVL.AUX_ATTENUATORS DO
    Att1 REF= BeamParam1.astAttenuators[idx];
    Att2 REF= BeamParam2.astAttenuators[idx];
    xAuxAttOK R= ( Att1.nTran > MIN( PMPS_GVL.TRANS_SCALING_FACTOR, ( (Att2.nTran *(PMPS_GVL.TRANS_SCALING_FACTOR+PMPS_PARAM.TRANS_MARGIN))/PMPS_GVL.TRANS_SCALING_FACTOR ) ) );
END_FOR

FOR idx:=1 to PMPS_GVL.MAX_APERTURES DO
    xAuxAprtOK R= BeamParam1.astApertures[idx].Height > BeamParam2.astApertures[idx].Height OR
    BeamParam1.astApertures[idx].Width > BeamParam2.astApertures[idx].Width;
END_FOR

F_SafeBPCompare := xAttOK AND xEvOK AND xRateOK AND xAuxAprtOK AND xAuxAttOK AND xBCOK;

END_FUNCTION
Related:

F_SafeBPCompare0Rate

(*Compares BeamParam1 to BeamParam2: if the parameters of BeamParam1 are more conservative than BeamParam2
the result will be true. *)
(* When the Mode PV is in Cu 0-rate means this function will return true regardless of other conditions if the beam rate
is zero *)
(* When the Mode PV is in SC 0-bc means this function will return true regardless of other conditions if the beam class
is zero *)
FUNCTION F_SafeBPCompare0Rate : BOOL
VAR_INPUT
    BeamParam1    :    ST_BeamParams; //Must be more conservative than 2
    BeamParam2    :    ST_BeamParams;
END_VAR
VAR

    // Beam-rate is zero, masks all others considerations.
    xZeroRate: BOOL;
    //Beam class is zero i.e beam off, masks all other considerations.
    xZeroBC : BOOL;

END_VAR
xZeroRate  := (BeamParam1.nRate = 0) AND (PMPS_GVL.stCurrentBeamParameters.nMachineMode = 0);
xZeroBC  := (BeamParam1.nBCRange <= 1) AND (PMPS_GVL.stCurrentBeamParameters.nMachineMode =1);

F_SafeBPCompare0Rate := (xZeroRate OR xZeroBC) OR F_SafeBPCompare(BeamParam1, BeamParam2);

END_FUNCTION
Related:

F_SetBeamParams

FUNCTION F_SetBeamParams : ST_BeamParams
VAR_INPUT
    nTran    :    REAL := 0;
    neVRange    :    DWORD := 0;
    nRate    :    UDINT := 0;
    nBCRange : WORD := 0;
    astAuxAtt : ARRAY [1..PMPS_GVL.AUX_ATTENUATORS] OF ST_PMPS_Attenuator;
END_VAR
VAR
    BeamParams    :    ST_BeamParams;

END_VAR
BeamParams.nTran := LIMIT(0,nTran,PMPS_GVL.TRANS_SCALING_FACTOR);
BeamParams.astAttenuators := astAuxAtt;
BeamParams.neVRange := neVRange;
BeamParams.nRate    := MIN(nRate,1000000);
BeamParams.nBCRange := MIN(nBCRange,32767);

F_SetBeamParams    := BeamParams;

END_FUNCTION
Related:

F_SetGoodAttStatus

//Use for testing purposes to set the attenuator status to a good state
FUNCTION F_SetGoodAttStatus : UINT
VAR_INPUT
    xStatus : UINT;
END_VAR
VAR
END_VAR
xStatus.0 := 1;
xStatus.3 := 1;
xStatus.4 := 1;

END_FUNCTION

F_SetStateParams

FUNCTION F_SetStateParams : BOOL;
VAR_INPUT
    nStateRef    :    UDINT;
    rPosition    :    REAL;
    rTolerance: REAL;
    stBeamParams    :    ST_BeamParams;
END_VAR
VAR_IN_OUT
    Table    :    FB_LinearDeviceStateTable;
END_VAR
VAR
    stDeviceState    :    ST_DeviceState;

END_VAR
stDeviceState.nStateRef := nStateRef;
stDeviceState.rPosition := rPosition;
stDeviceState.rTolerance := rTolerance;
stDeviceState.stReqBeamParam := stBeamParams;

Table.A_Add(
    key := nStateRef,
    putValue := stDeviceState
);

F_SetStateParams := Table.bOk;

END_FUNCTION
Related:

F_ValidReqID

FUNCTION F_ValidReqID : BOOL
VAR_INPUT
    ReqID : DWORD;
END_VAR
VAR
END_VAR
F_ValidReqID := ReqID <> PMPS_GVL.EXCLUDED_ASSERTION_ID AND ReqID <> 0;

END_FUNCTION
Related:

FB_Arbiter

(* FB Arbiter
A. Wallace 2020-6-26

The arbiter primary objectives are:
- Provide a simple interface for devices to request beam parameter sets
- Provide a way for devices to verify their BPS is active in the arbiter
- Provide a way for devices remove their requests from evaluation
- Evaluate all active beam parameter requests registered with the aribiter,
to determine the safest combination of all sets, provide this set as an output.
- Do all of this with minimal overhead

To these ends, the arbiter uses a hash-table, the rows being a state-id as the key, and a corresponding
 beam parameter set to be evaluated against all the other sets (or rows), in the table.

The hash table can be thought of as an array on steriods, they are worth reading about, suffice to say
the hash table will tell you when you reach the end of all the entries, and enables us to find entries quickly.

These features efficiently address the addition, removal, and verification of beam parameter sets listed in the above requirements.
*)
{attribute 'reflection'}
FUNCTION_BLOCK FB_Arbiter IMPLEMENTS I_HigherAuthority, I_LowerAuthority
VAR
    nRequestsCount : UDINT; // How many requests are currently in the arbiter

    {attribute 'pytmc' := '
        pv: AP
        io: i
        field: DESC Assertion Pool
     '}
    fbBPAssertionPool    :    FB_BeamParamAssertionPool; //Table of active beam parameter assertions

    xRequestMade : BOOL; // Arbiter has confirmed its request has made it into the beam parameter request

    {attribute 'pytmc' := '
        pv: ArbiterID
        io: i
        field: DESC Arbiter ID for elev. req.
     '}
    nArbiterID : UDINT; // Arbiter ID, used for making higher-level BP requests

    nNextCohort : UDINT := 1; // The cohort ID any new requests will adopt, will become ReqInProgCohort at the start of the next acknowledgement cycle
    nAckInProgCohort : UDINT := 0; // The cohort ID currently being acknowledged, will become nActiveCohort after acknowledgement from HA
    {attribute 'pytmc' := '
        pv: CohortCounter
        io: i
        field: DESC Intrnl cohort counter
     '}
    nActiveCohort : UDINT := 0; // Requests with cohorts <= to this value will be considered active in CheckRequest

    bStartNewAckRequest : BOOL; // Set by an add or remove method call, triggers an ack cycle
    bAckInProgress : BOOL; // Set by ElevateReq when there is a new ack request and reset when the ack cycle is complete

    // The following IDs are set by the last BP request that was most conservative
    // ie. other requests may limit the parameter, but this one is the most recent to limit it
    idTransmission : DWORD; // ID of BP limiting transmission
    idRate : DWORD; // ID of BP limiting rate

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

    sArbName : T_MaxString;

    InfoStringFmtr : FB_FormatString;

    bVerbose : BOOL := FALSE;
END_VAR
VAR_INPUT
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: ArbitratedBP
        io: i
        field: DESC Arbitrated BP
     '}
    q_stBeamParams    :    ST_BeamParams := PMPS_GVL.cstFullBeam; //Updated on each cycle of the arbiter FB with the current arbitrated beam parameter set

    q_xStateIDFound    :    BOOL; //Set true if a state-id is found in the assertion pool after calling A_VerifyAssertion
END_VAR


END_FUNCTION_BLOCK

// Adds a request to the arbiter pool.
// Returns true if the request was successfully added, false if not enough space or a request with the same ID is already present.
METHOD AddRequest : BOOL
VAR_INPUT
    nReqID    :    DWORD; // Unique ID within aribter for the request. Make sure this is unique for every device + state combination
    stReqBP        :    ST_BeamParams; //Requested beam params
    sDevName: STRING; // Name of the device making the request
END_VAR
VAR
    BP_Int : ST_BP_ArbInternal;
END_VAR
VAR_INST
    fbLog : FB_LogMessage;
END_VAR
// If the request is already in the pool, then skip the rest.
// This is flawed, needs to update the request if it's already in the pool, despite having the same ID
IF THIS^.CheckRequestInPool(nReqID)
    OR nReqID = PMPS_GVL.EXCLUDED_ASSERTION_ID
    OR nReqID = 0 THEN
    AddRequest := FALSE;
    RETURN;
END_IF


stReqBP.nCohortInt := nNextCohort; // Set the cohort number for this BP request.

// Pack the ID with the request
MEMCPY(ADR(BP_Int), ADR(stReqBP), SIZEOF(stReqBP));
BP_Int.nId := nReqID;
BP_Int.LiveInTable := TRUE;
BP_Int.sDevName := sDevName;

THIS^.fbBPAssertionPool.A_Add(
    key := nReqID,
    putValue := BP_Int
);


IF NOT THIS^.fbBPAssertionPool.bOk THEN
    fbLog(sMsg:=CONCAT('Addition to arbiter assertion pool failed ', DWORD_TO_HEXSTR(nReqID, 4, FALSE)),
            eSevr:=TcEventSeverity.Warning,
            eSubsystem:=E_Subsystem.MPS,
            sJson := F_PMPS_JSON(
                        sArbName,
                        sPath,
                        PMPS_CODES.ARB_FULL));
ELSIF THIS^.fbBPAssertionPool.bOk AND bVerbose THEN
    fbLog(sMsg:=CONCAT('Added to pool ',DWORD_TO_HEXSTR(nReqID, 4, FALSE)),
            eSevr:=TcEventSeverity.Verbose,
            eSubsystem:=E_Subsystem.MPS);
END_IF
// New request, make sure the ack cycle request flag is set
bStartNewAckRequest S= THIS^.fbBPAssertionPool.bOk;

AddRequest := THIS^.fbBPAssertionPool.bOk;

nRequestsCount := nEntryCount;
END_METHOD

// Kernel of the arbiter
// Logic for determining which beam parameter is the most conservative across all request sets.
{attribute 'no_check'}
METHOD INTERNAL ArbitrateBP : ST_BP_ArbInternal
VAR_INPUT
    stBP1    :    ST_BP_ArbInternal;
    stBP2    :    ST_BP_ArbInternal;
END_VAR
VAR
    idx : UINT := 1;
    bcBitmask : WORD:=0;
END_VAR
//<TODO>Add something here to register the key that won for each param

//Attenuation
ArbitrateBP.nTran := MIN(stBP1.nTran, stBP2.nTran);

FOR idx := 1 TO PMPS_GVL.AUX_ATTENUATORS DO
    ArbitrateBP.astAttenuators[idx].nTran := MIN(stBP1.astAttenuators[idx].nTran, stBP2.astAttenuators[idx].nTran);
END_FOR

//Apertures
FOR idx := 1 TO PMPS_GVL.MAX_APERTURES DO
    IF stBP1.astApertures[idx].Height = 0 OR stBP2.astApertures[idx].Height =0 THEN
        ArbitrateBP.astApertures[idx].Height := MAX(stBP1.astApertures[idx].Height, stBP2.astApertures[idx].Height);
    ELSE
        ArbitrateBP.astApertures[idx].Height := MIN(stBP1.astApertures[idx].Height, stBP2.astApertures[idx].Height);
    END_IF

    IF stBP1.astApertures[idx].Width = 0 OR stBP2.astApertures[idx].Width =0 THEN
        ArbitrateBP.astApertures[idx].Width := MAX(stBP1.astApertures[idx].Width, stBP2.astApertures[idx].Width);
    ELSE
        ArbitrateBP.astApertures[idx].Width := MIN(stBP1.astApertures[idx].Width, stBP2.astApertures[idx].Width);
    END_IF
END_FOR

//Photon energy ranges
ArbitrateBP.neVRange := stBP1.neVRange AND stBP2.neVRange;

//Beam Class ranges
ArbitrateBP.nBCRange := stBP1.nBCRange AND stBP2.nBCRange;

//Beam Class
//Assert the highest allowed beamclass
IF (ArbitrateBP.nBCRange = 0) THEN ArbitrateBP.nBeamClass := 0; END_IF
FOR idx:=1 TO 15 BY 1 DO
    IF (idx =1 ) THEN bcBitmask :=1; END_IF
    IF (ArbitrateBP.nBCRange AND bcBitmask) = bcBitmask THEN
        ArbitrateBP.nBeamClass := TO_USINT(idx);
    END_IF
    bcBitmask := ROL(bcBitmask,1);
END_FOR


//Rate
ArbitrateBP.nRate := MIN(stBP1.nRate, stBP2.nRate);
END_METHOD

(* Checks request ID is included in arbitration all the way to the accelerator interface
Use like so:
IF fbArbiter.CheckRequest(nStateIDAssertionToCheck) AND (other logic) THEN:
    Request is found and active in arbitration,. Do something.
ELSE:
    Request was not found, or is not yet included in arbitration. Don't do something/ wait.

*)
METHOD CheckRequest : BOOL
VAR_INPUT
    nReqID    :    DWORD;
END_VAR
VAR
    BP : ST_BeamParams;
END_VAR
fbBPAssertionPool.A_Lookup(key := nReqID);

// Verify BP is acknowledged
BP := fbBPAssertionPool.getValue;

// This logic:
// Did we find the assertion in the pool?
// Is the assertion cohort number less than the current cohort and greater than zero?
// Is the Aribter itself active in arbitration?
CheckRequest := fbBPAssertionPool.bOk AND
                BP.nCohortInt <= nActiveCohort AND
                BP.nCohortInt > 0;
END_METHOD

// Verify request is at least in the local arbiter
// Does not verify request has been included in arbitration.
// Use CheckRequest instead.
METHOD CheckRequestInPool : BOOL
VAR_INPUT
    nReqID    :    DWORD;
END_VAR
THIS^.fbBPAssertionPool.A_Lookup(key := nReqID);

CheckRequestInPool := THIS^.fbBPAssertionPool.bOk;
END_METHOD

// <Arbiter Internal>
// Elevates the arbitrated BP set to something above.
// Could be another arbiter, or a BP requester/ IO,
// or an FB that locks in a specific portion of the BP set.
METHOD ElevateRequest : BOOL
VAR_INPUT
    HigherAuthority : I_HigherAuthority;
END_VAR
(*
This method should only be used with one higher level component.
*)

// Complete previous ack cycle
IF bAckInProgress THEN
    IF HigherAuthority.CheckRequest(nReqID := nArbiterID) THEN
        nActiveCohort := nAckInProgCohort;
        bAckInProgress := FALSE;
    END_IF
END_IF

// Kick off another ack cycle
IF bStartNewAckRequest AND NOT bAckInProgress THEN
    nAckInProgCohort := nNextCohort;
    HigherAuthority.RemoveRequest(nReqID:= nArbiterID);
    bAckInProgress := HigherAuthority.RequestBP(nReqID := nArbiterID, stReqBP := GetArbitratedBP());
    IF bAckInProgress THEN
        bStartNewAckRequest := FALSE;
        nNextCohort := nNextCohort + 1;
    END_IF
END_IF
END_METHOD

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
    nID : DWORD := PMPS_GVL.EXCLUDED_ASSERTION_ID; // Arbiter ID, must be globally unique cannot be zero or 16#FFFF
END_VAR
nArbiterID := nId;
fbBPAssertionPool.A_Reset();

sArbName := CONCAT('Arbiter ID: 0x', DWORD_TO_HEXSTR(nID, 4, FALSE));
END_METHOD

// Executes Arbitration between all requested beam parameter sets
METHOD INTERNAL GetArbitratedBP : ST_BeamParams
VAR_INPUT
END_VAR
VAR
    getPosPtr    : POINTER TO T_HashTableEntry := 0;
    getBPStructInt    :    ST_BP_ArbInternal;
    stOutputBP    :    ST_BP_ArbInternal; //Holding struct for arbitration process
END_VAR
VAR_INST
    xFirstPass : BOOL := TRUE;
    fbGetCurTaskIdx : GETCURTASKINDEX;
    LastCycleCount : UDINT;
    fbLogMessage : FB_LogMessage;
END_VAR
//Arbitrate
(*
This step cycles through every hash table entry, comparing
the beam parameters from each assertion to what we're planning
to assert.

The safer of the two parameters is kept so at the end we may
have a mix of beam parameters, the composition being safe for
all asserters.

*)

THIS^.fbBPAssertionPool.A_GetFirst( putPosPtr := 0, getPosPtr=>getPosPtr, getValue=>getBPStructInt );
IF THIS^.fbBPAssertionPool.bOk THEN //If the table is empty (not OK), we are left with the default stOutputBP set above.

    //The first entry in the hash table is taken as the setting to arbitrate against
    stOutputBP := getBPStructInt;

    REPEAT
        THIS^.fbBPAssertionPool.A_GetNext( putPosPtr := THIS^.fbBPAssertionPool.getPosPtr, getPosPtr=>getPosPtr, getValue=>getBPStructInt );
        IF THIS^.fbBPAssertionPool.bOk THEN
            stOutputBP := ArbitrateBP(stOutputBP, getBPStructInt);
        END_IF
    UNTIL NOT fbBPAssertionPool.bOk
    END_REPEAT
ELSE
    //Full beam if there are no arbitration requests
    MEMCPY(ADR(stOutputBP), ADR(PMPS_GVL.cstFullBeam), SIZEOF(PMPS_GVL.cstFullBeam));
END_IF

MEMCPY(ADR(q_stBeamParams),ADR(stOutputBP), SIZEOF(q_stBeamParams));

GetArbitratedBP := stOutputBP;

GetArbitratedBP.xValidToggle := NOT GetArbitratedBP.xValidToggle;

xFirstPass := FALSE;
END_METHOD

(* Removes request from abritration. *)
METHOD RemoveRequest : BOOL
VAR_INPUT
    nReqId    :    DWORD;
END_VAR
VAR_INST
    fbLog : FB_LogMessage;
END_VAR
VAR CONSTANT
    BP_Int : ST_BP_ArbInternal := (nId:=0, LiveInTable:=FALSE);
END_VAR
IF nReqId <> PMPS_GVL.EXCLUDED_ASSERTION_ID AND CheckRequestInPool(nReqId) THEN
    // Include an A_Add action here to
    // update this entry with a dummy filler (ID = 0, LiveInTable= False)
    // before removing it since this hash
    // table needs to be viewed with a diagnostic
    // and so the entries need to appear clean.
    THIS^.fbBPAssertionPool.A_Add(key := nReqID, putValue := BP_Int);
    THIS^.fbBPAssertionPool.A_Remove(key := nReqId);

    IF bVerbose THEN
        IF THIS^.fbBPAssertionPool.bOk THEN
                fbLog(sMsg:=CONCAT('Removed from pool: ', DWORD_TO_HEXSTR(nReqID,4,FALSE)),
                eSevr:=TcEventSeverity.Verbose,
                eSubsystem:=E_Subsystem.MPS);
        ELSE
            fbLog(sMsg:=CONCAT('Failed to remove from pool: ', DWORD_TO_HEXSTR(nReqID,4,FALSE)),
                eSevr:=TcEventSeverity.Warning,
                eSubsystem:=E_Subsystem.MPS);
        END_IF
    END_IF

    // With any new request reset the internal arbiter "done" flag
    bStartNewAckRequest S= THIS^.fbBPAssertionPool.bOk;

    RemoveRequest := THIS^.fbBPAssertionPool.bOk;

    nRequestsCount := nEntryCount;

END_IF
END_METHOD

METHOD RequestBP : BOOL
VAR_INPUT
    (*StateID of state requesting beam parameter set*)
    nReqID  : DWORD;
    (*Requested beam params*)
    stReqBP : ST_BeamParams;
END_VAR
RequestBP := AddRequest(nReqID := nReqID, stReqBP := stReqBP, sDevName:=sArbName);
END_METHOD

// How many entries are in the arbiter now
PROPERTY nEntryCount : UDINT
VAR
END_VAR
fbBPAssertionPool.A_Count();
IF fbBPAssertionPool.bOk THEN
    nEntryCount := fbBPAssertionPool.nCount ;
END_IF
END_PROPERTY

PROPERTY nLowerAuthorityID : DWORD
VAR
END_VAR
nLowerAuthorityID := nArbiterID;
END_PROPERTY
Related:

FB_Arbiter_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_Arbiter_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
Arbitration();
RequestCheckRemoveBP();
FullArbitrationStack();
StackArbiters();
RemoveReq();
DoS();

END_FUNCTION_BLOCK

METHOD ArbiterCapacity
VAR_INPUT
END_VAR
VAR
    nId    :    DWORD := 1;
    stReq    :    ST_BeamParams := (nTran:=12);

    nRandID : DWORD;
    testReq : ST_BeamParams := (neVRange := 16#FFEE, nRate:=33);

    idx : DWORD;

END_VAR
VAR_INST
    fbArbCapacityTest    :    FB_Arbiter(1);

    Cycle : INT := 0;

    CycleLimit : INT := 20;

END_VAR
VAR CONSTANT
    SysID : DWORD := 42;
END_VAR
TEST('Arbiter Capcity');
    // Fill the arbiter
    FOR idx := 1 TO PMPS_PARAM.MAX_ASSERTIONS DO
        fbArbCapacityTest.AddRequest(nReqID := idx, stReqBP := testReq, sDevName:= CONCAT('Device #', TO_STRING(idx)));
    END_FOR

TEST_FINISHED();
END_METHOD

METHOD Arbitration
VAR_INPUT
END_VAR
VAR

     nTranReqID    :    UDINT    := 1;
    stAttReqBP    :    ST_BeamParams := PMPS_GVL.cstFullBeam;

    nPPEReqID    :    UDINT    := 2;
    stPPEReqBP    :    ST_BeamParams := PMPS_GVL.cstFullBeam;

    nEVReqID    :    UDINT    := 3;
    stEVReqBP    :    ST_BeamParams := PMPS_GVL.cstFullBeam;

    nRateReqID    :    UDINT    := 4;
    stRateReqBP    :    ST_BeamParams := PMPS_GVL.cstFullBeam;

    nBCRangeReqID : UDINT    := 5;
    stBCReqBP    :    ST_BeamParams := PMPS_GVL.cstFullBeam;

    stArbitratedBP    :    ST_BeamParams;// :=  PMPS_GVL.cstFullBeam;;
    stExpectedBP    :    ST_BeamParams := PMPS_GVL.cstFullBeam;

END_VAR
VAR_INST
    fbArbiter    :    FB_Arbiter(1);
END_VAR
TEST('Arbitration');

stAttReqBP.nTran := 0.50;
stEVReqBP.neVRange := 2#1111_1111_1111_1111_1111_1100_1111_1111;
stRateReqBP.nRate := 10;
stBCReqBP.nBCRange := 2#0111_0000_0000_0000;
stBCReqBP.nBeamClass := 15;

stExpectedBP.nTran     := stAttReqBP.nTran;
stExpectedBP.neVRange := stEVReqBP.neVRange;
stExpectedBP.nRate := stRateReqBP.nRate;
stExpectedBP.nBCRange := stBCReqBP.nBCRange;
stExpectedBP.nBeamClass := stBCReqBP.nBeamClass;


//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nTranReqID, stAttReqBP, sDevName:= 'Device' ),
                    'Safest attenuation request failed to load');

//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nPPEReqID, stPPEReqBP, sDevName:= 'Device'),
                    'Safest PPE request failed to load');

//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nEVReqID, stEVReqBP, sDevName:= 'Device'),
                    'Safest stEVReqBP request failed to load');

//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nRateReqID, stRateReqBP, sDevName:= 'Device'),
                    'Safest stRateReqBP request failed to load');

//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nBCRangeReqID, stBCReqBP, sDevName:= 'Device'),
                    'Safest stBCReqBP request failed to load');

stArbitratedBP := fbArbiter.GetArbitratedBP();

AssertFalse(F_DifferentBeamParams(stExpectedBP, stArbitratedBP),
                'Arbitrated BP does not match expected.');

TEST_FINISHED();
END_METHOD

METHOD DoS
VAR



    nOrigReqID : UDINT := 30;
    nSecondReqID : UDINT := 35;

    nPeskyReqID : UDINT := 40;


END_VAR
VAR_INST
    DosArb : FB_Arbiter(20);

    DummyHA : FB_DummyHA := (ReqAcknowledged:=FALSE); // Set req ack false to start so we can control when it is verified
    nTimeoutCounter : UINT;
    bInit : BOOL;
    bSecond : BOOL;
    eDosTestCases : E_ArbDosTestStates;
END_VAR
VAR CONSTANT
        nTimeoutCounterMAX : UINT := 50;
        nWaitCount : UINT := 10;
        nWait2ndCount : UINT := nWaitCount + 5;
END_VAR
TEST('DoS');

CASE eDosTestCases OF
    E_ArbDosTestStates.Init: // Init, first request
        DosArb.AddRequest(nOrigReqID, PMPS_GVL.cst0RateBeam, sDevName:= 'Device');
        eDosTestCases := E_ArbDosTestStates.SecondReq;

    E_ArbDosTestStates.SecondReq: // Add second request
        DosArb.AddRequest(nSecondReqID, PMPS_GVL.cst0RateBeam, sDevName:= 'Device');
        eDosTestCases := E_ArbDosTestStates.WaitForAck;

    E_ArbDosTestStates.WaitForAck:
        IF nTimeoutCounter > nWaitCount THEN
            DummyHA.ReqAcknowledged := TRUE;
            eDosTestCases := E_ArbDosTestStates.WaitFor2ndAck;
        END_IF

    E_ArbDosTestStates.WaitFor2ndAck:
        DummyHA.ReqAcknowledged := False;
        IF nTimeoutCounter > nWait2ndCount THEN
            DummyHA.ReqAcknowledged := TRUE;
            eDosTestCases := E_ArbDosTestStates.RunAsserts;
        END_IF

END_CASE

DosArb.ElevateRequest(DummyHA);

// Pesky request always adding and removing
DosArb.RemoveRequest(nPeskyReqID);
DosArb.AddRequest(nPeskyReqID, PMPS_GVL.cst0RateBeam, sDevName:= 'Device');


nTimeoutCounter := nTimeoutCounter + 1;

IF nTimeoutCounter > nTimeoutCounterMAX or eDosTestCases = E_ArbDosTestStates.RunAsserts THEN
    AssertTrue(DosArb.CheckRequest(nOrigReqID), 'Original request still not accepted');
    AssertTrue(DosArb.CheckRequest(nSecondReqID), 'Second request still not accepted');

    TEST_FINISHED_NAMED('DoS');
END_IF
END_METHOD

{attribute 'no_check'}
METHOD FullArbitrationStack
VAR_INPUT
END_VAR
VAR
    nId    :    DWORD := 1;
    stReq    :    ST_BeamParams := (nTran:=12);

    nIdDoS : DWORD := 13;
    stDoSReq : ST_BeamParams := PMPS_GVL.cstFullBeam;
END_VAR
VAR_INST
    fbArbiter    :    FB_Arbiter(1);

    fbHigherArb : FB_Arbiter(2);

    fbSubSysToArb : FB_SubSysToArbiter_IO; // Subsystem interface with beamline arbiter PLC
    pBPR : POINTER TO ST_BeamParams_IO;

    fbArbToSubSys : FB_ArbiterToSubSys_IO := (RequestingSystemID:=42); // Beamline arbiter PLC interface with subsystems
    pBPC : POINTER TO ST_BeamParams_IO;

    fbBPR : FB_BPRequestor; // Use in the arbiter PLC to apply an arbitrated beam parameter set to the accelerator interface

    FFO : FB_HardwareFFOutput; // Dummy FFO not important for these tests

    Cycle : INT := 0;

    CycleLimit : INT := 20;

END_VAR
VAR CONSTANT
    SysID : DWORD := 42;
END_VAR
// This simulates a synchronous cycle between the subsystem and arbiter PLC cycles. Ie. phase is locked, and cycle time is the same. This is not always the case, so these tests should be
// trusted with a grain of salt.

TEST('FullStack');



CASE Cycle OF

0:
    IF NOT fbArbiter.CheckRequestInPool(nID) THEN
        AssertTrue(fbArbiter.AddRequest(nId, stReq,'Device'), 'Arbiter returned false from AddRequest'); // some device asking its local arbiter for some beam parameters
    END_IF

    TEST('JustEnteredRequest');
    AssertFalse(fbArbiter.CheckRequest(nId),
        'Check should fail. Request not arbitrated yet.');
    TEST_FINISHED();

8000:
    TEST_FINISHED_NAMED('FullStack');

END_CASE


TEST('VerifyBPRHasBP');
IF fbHigherArb.CheckRequest(SysID) THEN
    AssertFalse(F_DifferentBeamParams(stReq, fbBPR.q_ReqBP),
        'Beam parameters not fully propagated to BPR at highest level');
        TEST_FINISHED_NAMED('VerifyBPRHasBP');
END_IF

TEST('ArbToSubsysPropagation');
IF fbArbToSubSys.nActiveCohort = fbArbToSubSys.nRequestedCohort
    AND fbArbToSubSys.nActiveCohort <> 0 THEN
    AssertTrue(fbHigherArb.CheckRequest(SysID),
        'Active cohort = Requested cohort before higher arbiter confirmed request was active');
    TEST_FINISHED_NAMED('ArbToSubsysPropagation');
END_IF

TEST('SubsysToArbPropagation');
IF fbArbiter.CheckRequest(nId) THEN
    AssertTrue(fbSubSysToArb.nActiveCohort >= fbSubSysToArb.nRequestCohort,
        'Request validated before higher level confirmation');
    TEST_FINISHED_NAMED('SubsysToArbPropagation');
END_IF


TEST('VerifyIncludedAtAllLevels');
IF fbArbiter.CheckRequest(nId) THEN
    AssertTrue(fbHigherArb.CheckRequest(fbArbToSubSys.RequestingSystemID), 'HigherArb is not yet included in arbitration');
    AssertTrue(fbSubSysToArb.CheckRequest(fbArbiter.nLowerAuthorityID), 'Local Arb is not yet included in arbitration');
    TEST_FINISHED_NAMED('VerifyIncludedAtAllLevels');
END_IF


///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////

//Ethercat transfer simulation
// Transfer of requested BP to arbiter PLC
pBPR := ADR(fbArbToSubSys.i_RequestedBP);
pBPR^ := fbSubSysToArb.q_stRequestedBP;

// Transfer of current BP to sub system PLC
pBPC := ADR(fbSubSysToArb.i_stCurrentBP);
pBPC^ := fbArbToSubSys.o_CurrentBP;

//////////////////////////////////////
//Start of arbiter plc cycle
//Arb io collects sub system status
fbArbToSubSys(Arbiter := fbHigherArb, fbFFHWO := FFO); // This is making a request in the higher arb
//BPR collects arbiter summation
fbBPR(Arbiter := fbHigherArb); // This is getting the arbitrated BP




IF CycleLimit > Cycle THEN
    Cycle := Cycle + 1;
ELSE
    Cycle := 8000;
END_IF
END_METHOD

METHOD RemoveReq
VAR

    Arb : FB_Arbiter(20);

    DummyHA : FB_DummyHA;

    Result : BOOL;

END_VAR
Arb.AddRequest(30, PMPS_GVL.cst0RateBeam, 'Device');

Arb.ElevateRequest(DummyHA);

TEST('Remove Req');
    Result := Arb.RemoveRequest(77);

    AssertFalse( Result, 'RemoveReq returned true, with nothing to remove');
    AssertFalse( Arb.bStartNewAckRequest , 'StartNewAckRequest is set. Something is wrong');

TEST_FINISHED();
END_METHOD

METHOD RequestCheckRemoveBP
VAR_INPUT
END_VAR
VAR

    nId    :    DWORD := 1;
    stReq    :    ST_BeamParams;
END_VAR
VAR_INST
    fbArbiter    :    FB_Arbiter(1);
END_VAR
TEST('RequestBP');
AssertTrue(fbArbiter.AddRequest(nId, stReq, 'Device'),
    'Request failed');

TEST_FINISHED();

TEST('DuplicateID');
AssertFalse(fbArbiter.AddRequest(nId, stReq, 'Device'),
    'Duplicate request accepted, not good.');

TEST_FINISHED();

TEST('CheckBP');
AssertFalse(fbArbiter.CheckRequest(nId),
    'Check should fail. Request not arbitrated yet.');
TEST_FINISHED();

TEST('CheckBPArbitrated');
WRITE_PROTECTED_ULINT(ADR(fbArbiter.nActiveCohort), fbArbiter.nNextCohort);
AssertTrue(fbArbiter.CheckRequest(nId),
    'Check should pass.');

TEST_FINISHED();

TEST('IDDoesNotExist');

AssertFalse(fbArbiter.CheckRequest(nId+1),
    'Check returned true when ID does not exist');

TEST_FINISHED();

TEST('RemoveBP');
AssertTrue(fbArbiter.RemoveRequest(nId),
    'Remove failed');
AssertFalse(fbArbiter.CheckRequest(nId),
    'Remove did not remove request');

TEST_FINISHED();

TEST('NoRequestsFullBeam');

AssertFalse(F_DifferentBeamParams(fbArbiter.GetArbitratedBP(), PMPS_GVL.cstFullBeam), 'Arbitrated beam should be full with all requests removed');

TEST_FINISHED();
END_METHOD

METHOD StackArbiters
VAR_INPUT
END_VAR
VAR
    nId    :    DWORD := 1;
    stReq    :    ST_BeamParams := (nTran:=12);
END_VAR
VAR_INST
    fbArbiter    :    FB_Arbiter(1);

    fbHigherArb : FB_Arbiter(2);

    ArbBP : ST_BeamParams;

    Cycle : INT := 0;

    CycleLimit : INT := 20;

END_VAR
VAR CONSTANT
    SysID : DWORD := 42;
END_VAR
TEST('StackingArbiters');
fbArbiter.AddRequest(nReqID:=nId, stReqBP:=stReq, sDevName :='Device');

fbArbiter.ElevateRequest(fbHigherArb);

ArbBP := fbHigherArb.GetArbitratedBP();

AssertTrue(ArbBP.nTran = 12, 'We should see the transmission number here');

TEST_FINISHED();
END_METHOD
Related:

FB_ArbiterToSubSys_IO

(* Exchanges preemption requests and current beam state with subsystems in arbiter
network.

2019-12-2, A. Wallace

Use in conjunction with FB_SubSysToArbiter_IO.

This FB runs in the beamline arbiter PLC (BAP). There should be one of these for every
subsystem interfaced to the BAP. Each subsystem PLC should run a sibling FB_SubSysToArbiter_IO FB.

This FB and its subordinate communicate through the ethercat connection between the subsystem PLC and the BAP.

A beam parameter set request is sent from the subsystem to this PLC, and includes a cohort number.

This cohort number (i_RequestedBP.nCohortInt) is compared to a local cohort number (nRequestedCohort).
A request from the subsystem to update or modify its current request is indicated by an increase in the
i_RequestedBP.nCohortInt value. Then this function block updates the local arbiter request for the subsystem
and awaits confirmation. Once the new request is confirmed in the preemptive system this function block increments
the ActiveCohort value to match the nRequestedCohort. ActiveCohort is also relayed back to the subsystem
as the active cohort value in the o_CurrentBP set. The subsystem then knows its request has been heard,
and can carry out its own acknowledgement to its various requestors.

Things to note:
If a subsystem has any new or changed preemptive requests you should see the cohort value increment a bit.
If the cohort number is incrementing out of control, it usually means the subsysetm has some preemptive
request loop (two arbiters elevating to each other), or there is a RequestAdd, RequestRemove loop.

*)
FUNCTION_BLOCK FB_ArbiterToSubSys_IO
VAR_INPUT
    RequestingSystemID      :       DWORD := PMPS_GVL.EXCLUDED_ASSERTION_ID; // System ID for making requests to the supreme arbiter
    sName : STRING := 'ArbiterToSubSys';
    Reset : BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
    Arbiter : FB_Arbiter;
    fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR

    i_RequestedBP AT %I*    :       ST_BeamParams_IO; // Requested BP from subsystem
    {attribute 'pytmc' := '
        pv: RequestedBP:FromSubSys
    '}
    RequestedBP: ST_BeamParams;

    o_CurrentBP AT %Q* : ST_BeamParams_IO; // Current BP set

    // EL6692 Diagnostics
    i_Connected AT %I* : BOOL; // SYNC Inputs^External device not connected !!! Doesn't really work.
    {attribute 'pytmc' := 'pv: WcState
        io: i
        field: DESC Working counter state.
        field: ZNAM OK
        field: ONAM Error'}
    i_WcState AT %I* : BOOL := TRUE; // WcState^WcState
    {attribute 'pytmc' := 'pv: TxPDO_state
        io: i
        field: DESC PDO Transmission is OK
        field: ZNAM OK
        field: ONAM Error'}
    i_TxPDOState AT %I* : BOOL := TRUE; // SYNC Inputs^TxPDO state
    {attribute 'pytmc' := 'pv: TxPDO_toggle
        io: i
        field: DESC PDO Transmission is OK
        field: ZNAM OK
        field: ONAM Error'}
    i_TxPDOToggle AT %I* : BOOL; // TxPDO toggle

    // Fast faults
    ffPMPSIO_Disconnect : FB_FastFault := (i_Desc:='Issue w/ arbiter interface to subsystem, verify subsystem is OK, or ethercat connection.'); // Fast fault for ethercat issues
    nActiveCohort : UDINT := 0; // Active cohort. Updated to the req. number from the sub system after request is confirmed.
    nRequestedCohort : UDINT := 0;
    rtToggle : R_TRIG;


END_VAR
// Incoming request from subsystem PLC
RequestedBP := IO_TO_BP(i_RequestedBP);

// Update Arbiter with our request
rtToggle(CLK:= i_RequestedBP.xValid); // If interface resumes, reset/catch up
// xValid will go false if the PLC program on the other side is stopped. Ironically the ethercat interface continues happily.
// If subsystem cohort has incremented, update beam parameter request with our arbiter
IF rtToggle.Q THEN
    nActiveCohort := 0;
END_IF

IF i_RequestedBP.nCohortInt > nRequestedCohort OR rtToggle.Q THEN
    Arbiter.RemoveRequest(RequestingSystemID);
    Arbiter.AddRequest(RequestingSystemID, RequestedBP,sName);
    nRequestedCohort := i_RequestedBP.nCohortInt;
END_IF


// Check if latest request is acknowledged by this arbiter
// Acknowledgement happens after an arbitration cycle, which means if RequestedCohort just incremented
// due to a new request, CheckRequest will be false.
IF Arbiter.CheckRequest(RequestingSystemID) THEN
    nActiveCohort := nRequestedCohort; // If it is, then the active cohort is set to the requested cohort number.
END_IF

// Sending current beam parameter set to subsystems
o_CurrentBP := BP_TO_IO(PMPS_GVL.stCurrentBeamParameters);
o_CurrentBP.nCohortInt := nActiveCohort;


//////////////////////////////////////
// Verifying the ethercat interface health
/////////////////////////////////////
// Both of these values should be 0 for a fully valid exchange
ffPMPSIO_Disconnect.i_xOK := NOT i_WcState AND NOT i_TxPDOState;

ffPMPSIO_Disconnect(
    io_fbFFHWO := fbFFHWO,
    i_xReset := Reset,
    i_DevName := sName,
    i_TypeCode:=5);

END_FUNCTION_BLOCK
Related:

FB_ArbToSubsys_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_ArbToSubsys_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
BasicFunction();

END_FUNCTION_BLOCK

{attribute 'no_check'}
METHOD BasicFunction
VAR_INPUT
END_VAR
VAR CONSTANT
    SysID : DWORD := 42;
END_VAR
VAR
    nId    :    DWORD := 1;
    stReq    :    ST_BeamParams := (nTran:=12);
END_VAR
VAR_INST
    fbArbiter    :    FB_Arbiter(1);

    pBPR : POINTER TO ST_BeamParams_IO;

    stBPRIO : ST_BeamParams_IO := (
        nCohortInt := 1,
        nTran := 35); // Simulates subsystem BP request

    fbArbToSubSys : FB_ArbiterToSubSys_IO := (RequestingSystemID:=42); // Beamline arbiter PLC interface with subsystems
    pBPC : POINTER TO ST_BeamParams_IO;

    fbBPR : FB_BPRequestor; // Use in the arbiter PLC to apply an arbitrated beam parameter set to the accelerator interface

    FFO : FB_HardwareFFOutput; // Dummy FFO not important for these tests

    Cycle : INT := 0;

    CycleLimit : INT := 20;

END_VAR
// Simulate an initial cycle with no new requests coming in
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);

TEST('Init');
TEST_FINISHED();

//Ethercat transfer simulation
// Transfer of requested BP to arbiter PLC
pBPR := ADR(fbArbToSubSys.i_RequestedBP);
pBPR^ := stBPRIO;

///////////////////////////////////////
//Sub system cycle
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
//END of sub system cycle
///////////////////////////////////////


TEST('New request');
    AssertTrue(fbArbToSubSys.nRequestedCohort = fbArbToSubSys.i_RequestedBP.nCohortInt, 'ReqCohort and qBP Cohort do not match, and they should');
    AssertTrue(fbArbiter.CheckRequestInPool(SysID), 'ArbToSubSys request should be in arbiter pool now');
    AssertTrue(fbArbToSubSys.nActiveCohort = 0, 'Active cohort should remain the initial value until the current request is active in higher arbitration');
    AssertTrue(fbArbToSubSys.o_CurrentBP.nCohortInt = fbArbToSubSys.nActiveCohort, 'Cohort being sent to sub system remains unincremented');
TEST_FINISHED_NAMED('New request');


///////////////////////////////////////
//Sub system cycle
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
//END of sub system cycle
///////////////////////////////////////

///////////////////////////////////////
//Sub system cycle
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
//END of sub system cycle
///////////////////////////////////////

///////////////////////////////////////
//Sub system cycle
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
//END of sub system cycle
///////////////////////////////////////

TEST('Request has become active');
    AssertTrue(fbArbToSubSys.nRequestedCohort = fbArbToSubSys.nActiveCohort, 'ReqCohort and qBP Cohort do not match, and they should');
    AssertTrue(fbArbiter.CheckRequest(SysID), 'ArbToSubSys request should be in arbiter pool now');
    AssertTrue(fbArbToSubSys.nActiveCohort = stBPRIO.nCohortInt, 'Active cohort should remain the initial value until the current request is active in higher arbitration');
    AssertTrue(fbBPR.q_ReqBP.nTran = stBPRIO.nTran, 'BPR and requested BP should match');
TEST_FINISHED_NAMED('Request has become active');
END_METHOD
Related:

FB_AttenuatorSimulator

FUNCTION_BLOCK FB_AttenuatorSimulator
VAR_INPUT
    i_rRequestedAttenuation    :    REAL;
    i_stBPReadback    :    ST_BeamParams;
END_VAR
VAR_OUTPUT
    q_xFault    :    BOOL;
    q_rCurrentAttenuation    :    REAL;
END_VAR
VAR_IN_OUT
    Arbiter    :    FB_Arbiter; //Higher level arbiter from which upstream attenuation can be requested
END_VAR
VAR
    tonDelay    :    TON := (
        PT := T#3S
        );
END_VAR
// Really basic delay
tonDelay(IN := i_rRequestedAttenuation <> q_rCurrentAttenuation);

IF tonDelay.Q THEN
    q_rCurrentAttenuation := i_rRequestedAttenuation;
END_IF

END_FUNCTION_BLOCK
Related:

FB_BeamClassFromEPICS

FUNCTION_BLOCK FB_BeamClassFromEPICS

(*
enum MPSBeamClass {
    Beam Off    =  0,
    Kicker STBY =  1,
    BC1Hz       =  2,
    BC10Hz      =  3,
    Diagnostic  =  4,
    BC120Hz     =  5,
    Tuning      =  6,
    1% MAP      =  7,
    5% MAP      = 8,
    10% MAP     = 9,
    25% MAP     = 10,
    50% MAP     = 11,
    100% MAP    = 12,
    FULL        = 13,
    SPARE       = 14,
    SPARE       = 15
}
*)

VAR_IN_OUT
    BP : ST_BeamParams;
    fbMPS_BeamClass : FB_LREALFromEPICS;
    FFO : FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
    xError : BOOL;
END_VAR
VAR


    ffBeamClassReadBack : FB_FastFault := (
        i_DevName := 'Arbiter',
        i_Desc := 'Issue with beam class readback from Accelerator. Gateway or EPICS connection. Must be fixed.',
        i_TypeCode := 16#213,
        i_xAutoReset:=True);
END_VAR
VAR CONSTANT
    cFailSafeBC : USINT := 16;
END_VAR
fbMPS_BeamClass();

IF fbMPS_BeamClass.bValid THEN
    BP.nBeamClass := LREAL_TO_USINT(fbMPS_BeamClass.fValue);
ELSE
    BP.nBeamClass := cFailSafeBC;
END_IF
BP.nBCRange := TO_WORD(BP.nBeamClass);

ffBeamClassReadback(i_xOK:=fbMPS_BeamClass.bValid, io_fbFFHWO:=FFO);

BP.xValid R= NOT fbMPS_BeamClass.bValid;

END_FUNCTION_BLOCK
Related:

FB_BeamClassOutputs

(*
Sets the beam class assertion lines for a given beam class.
*)
{attribute 'no_check'}
FUNCTION_BLOCK FB_BeamClassOutputs
VAR_INPUT
    BP : ST_BeamParams;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    nBeamClass   :    BYTE;
    wBeamClass    :    BYTE;

    InitCounter: BYTE;
    counter    :    INT;

    // Beam class lines are restricted to 8 channels in the current design, since
    // there are no plans to use all 16. Channels 1-7 may be allocated to any other
    // beam classes so long as they are ordered least to greatest.
    // Channel 8 is reserved for full beam.
    {attribute 'pytmc' := 'pv: BeamClassChannel
        io: i
        field: DESC Hardwire channel state'}
        epicsBitmap : WORD;

    {attribute 'TcLinkTo' := '[1] := TIIB[PMPS_Premp]^Channel 1^Output;
                              [2] := TIIB[PMPS_Premp]^Channel 2^Output;
                              [3] := TIIB[PMPS_Premp]^Channel 3^Output;
                              [4] := TIIB[PMPS_Premp]^Channel 4^Output;

                              [5] := TIIB[PMPS_Premp]^Channel 5^Output;
                              [6] := TIIB[PMPS_Premp]^Channel 6^Output;
                              [7] := TIIB[PMPS_Premp]^Channel 7^Output;
                              [8] := TIIB[PMPS_Premp]^Channel 8^Output;'} // 8 - Full beam
    q_BC_ASSERTION_LINES    AT    %Q*    : ARRAY [1..MAX_BEAM_CLASS_LINES] OF    BOOL;
END_VAR
VAR CONSTANT
    //Limited to total of 6 digital outputs
    MAX_BEAM_CLASS_LINES : BYTE := 8;
    BC_1HZ : BYTE := 1;
    BC_10HZ : BYTE := 2;
    BC_FULL : BYTE := 16;
END_VAR
// Determine BC
IF BP.nRate >= 120 THEN
    nBeamClass := BC_FULL;
ELSIF BP.nRate >= 10 THEN
    nBeamClass := BC_10HZ;
ELSIF BP.nRate >= 1 THEN
    nBeamClass := BC_1HZ;
ELSE
    nBeamClass := 0;
END_IF


//Assert Beam Class
////////////////////////////////////
//0x0 = 0000 0000 0000 0000
//0x1 = 0000 0000 0000 0001
//0xF = 1111 1111 1111 1111

//Initialize BC lines to zero on every pass
FOR InitCounter := 1 TO MAX_BEAM_CLASS_LINES DO
    q_BC_ASSERTION_LINES[InitCounter] := FALSE;
END_FOR

//Set BC lines according to beam class
//A BC of 0x0 would pass over this loop, setting none of the lines high
// , as FOR loops check the initialized variable at the top to see if it's >
// than the "TO" variable.
FOR wBeamClass:=1 TO MIN(MAX_BEAM_CLASS_LINES-1, nBeamClass) DO
    q_BC_ASSERTION_LINES[wBeamClass] := TRUE;
END_FOR

q_BC_ASSERTION_LINES[8] := nBeamClass = 16; //Set channel 8 true if BC is 16


// Readbacks for EPICS
epicsBitmap.0 := q_BC_ASSERTION_LINES[1];
epicsBitmap.1 := q_BC_ASSERTION_LINES[2];
epicsBitmap.2 := q_BC_ASSERTION_LINES[3];
epicsBitmap.3 := q_BC_ASSERTION_LINES[4];

epicsBitmap.4 := q_BC_ASSERTION_LINES[5];
epicsBitmap.5 := q_BC_ASSERTION_LINES[6];
epicsBitmap.6 := q_BC_ASSERTION_LINES[7];
epicsBitmap.7 := q_BC_ASSERTION_LINES[8];

END_FUNCTION_BLOCK
Related:

FB_BeamClassOutputs_BCD

(*
Sets the beam class assertion lines for a given beam class.
*)
{attribute 'no_check'}
FUNCTION_BLOCK FB_BeamClassOutputs_BCD
VAR_INPUT
    BP : ST_BeamParams;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    nBeamClass   :    BYTE;
    wBeamClass    :    BYTE;

    InitCounter: BYTE;
    counter    :    INT;

    // Beam class lines are restricted to 4 channels in the current design.
    // There are 16 possible beamclasses with 0 -15 with 0 no beam.
    // First out put is the LSB and bit 4 is MSB
    {attribute 'pytmc' := 'pv: BeamClassChannel
        io: i
        field: DESC Hardwire channel state'}
        epicsBitmap : WORD;

    {attribute 'TcLinkTo' := '[1] := TIIB[PMPS_Premp]^Channel 1^Output;
                              [2] := TIIB[PMPS_Premp]^Channel 2^Output;
                              [3] := TIIB[PMPS_Premp]^Channel 3^Output;
                              [4] := TIIB[PMPS_Premp]^Channel 4^Output;'}
    q_BC_ASSERTION_LINES    AT    %Q*    : ARRAY [1..MAX_BEAM_CLASS_LINES] OF    BOOL;
END_VAR
VAR CONSTANT
    //Limited to total of 4 digital outputs
    MAX_BEAM_CLASS_LINES : BYTE := 4;

END_VAR
//Initialize BC lines to zero on every pass
FOR InitCounter := 1 TO MAX_BEAM_CLASS_LINES DO
    q_BC_ASSERTION_LINES[InitCounter] := FALSE;
END_FOR

//Set BC lines according to beam class
// There are 4 digital outputs the BC is going to be a BCD
q_BC_ASSERTION_LINES[1] := BP.nBeamClass.0;
q_BC_ASSERTION_LINES[2] := BP.nBeamClass.1;
q_BC_ASSERTION_LINES[3] := BP.nBeamClass.2;
q_BC_ASSERTION_LINES[4] := BP.nBeamClass.3;

// Readbacks for EPICS
epicsBitmap.0 := q_BC_ASSERTION_LINES[1];
epicsBitmap.1 := q_BC_ASSERTION_LINES[2];
epicsBitmap.2 := q_BC_ASSERTION_LINES[3];
epicsBitmap.3 := q_BC_ASSERTION_LINES[4];

END_FUNCTION_BLOCK
Related:

FB_BeamClassWatcher

(*
M. Ghaly
The Beam class watcher ensures the current and target beam class is within
the arbirated bounds.


The abritrated bounds come from a simple AND of all the permitted beam class ranges. See
the arbitrate action of the arbiter FB. It also reads a PV and ensures that the asserted Beam Class
is the same as the PV from the ATCA crate

Note, this protection logic does not account for beam-off when determining fast-fault
status. If a device is requesting a limited range of BC, this request must be honored,
regardless of current beam-rate.
*)
{attribute 'reflection'}
FUNCTION_BLOCK FB_BeamClassWatcher
VAR_INPUT
    i_stCurrentBeamParams    :    ST_BeamParams; //Link to global beam params
    i_stMachineTargetBeamParams    :    ST_BeamParams; //Link to global machine target beam params
    i_stRequestedBeamParams    :    ST_BeamParams; //Link to arbiter output or beam param. requestor
    //Auto Reset fault
    i_xAutoReset: BOOL;
    sName : STRING := 'BeamClassWatcher';
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR
    xBeamClassWithinBounds    :    BOOL;
    fbFF    :    FB_FastFault :=(
        i_DevName := sName,
        i_Desc := 'Fault occurs when the asserted beamclass or pv beam class falls outside the permitted range.',
        i_TypeCode := 7 );

   {attribute 'pytmc' := '
        pv: ResidualBeamClass
        io: i
        archive: 1Hz monitor
        field: EGU bc-bitmask
    '}
    bcResidual : DWORD;

    fbLog : FB_LogMessage := (
        eSubSystem := E_Subsystem.MPS,
        eSevr := TcEventSeverity.Critical
        );
    bLogOneShot : BOOL;
    sDevName : T_MaxString := 'Beam Class Watcher';

    fbGetHN : FB_GetHostName;
    bInit : BOOL := TRUE;

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

    fbStr : FB_FormatString := (
        sOut := 'Non-zero beam class residual: %32b; Req: %32b; Act: %32b');
END_VAR
IF bInit THEN
    fbGetHN(bExecute:=TRUE);
    bInit R= NOT fbGetHN.bBusy;
END_IF

xBeamClassWithinBounds := (i_stCurrentBeamParams.nBCRange AND i_stRequestedBeamParams.nBCRange) = i_stCurrentBeamParams.nBCRange
                    AND  (i_stCurrentBeamParams.nBeamClass <= i_stRequestedBeamParams.nBeamClass);

bcResidual := (i_stCurrentBeamParams.nBCRange XOR i_stRequestedBeamParams.nBCRange) AND i_stCurrentBeamParams.nBCRange;

IF bcResidual <> 0 AND bLogOneShot THEN
    fbLog.sJson := F_PMPS_JSON(
        CONCAT(fbGetHN.sHostName, sDevName),
        sPath,
        PMPS_CODES.PEW_FAULT);

    fbStr.arg1 := F_DWORD(bcResidual);
    fbStr.arg2 := F_WORD(i_stCurrentBeamParams.nBCRange);
    fbStr.arg3 := F_WORD(i_stRequestedBeamParams.nBCRange);

    fbStr();

    fbLog(sMsg:=fbStr.sOut);
    bLogOneShot := FALSE;
ELSIF bcResidual = 0 THEN
    bLogOneShot := TRUE;
END_IF

fbFF(i_xOK := xBeamClassWithinBounds OR (i_stCurrentBeamParams.nMachineMode = 0),
    io_fbFFHWO := io_fbFFHWO, i_xAutoReset := i_xAutoReset);

END_FUNCTION_BLOCK
Related:

FB_BeamParamAssertionPool

(* This function block implements simple database. Data element values are stored in the hash table.  *)
{attribute 'no_check'}
FUNCTION_BLOCK FB_BeamParamAssertionPool
VAR_INPUT
    key         : DWORD := 0;(* Entry key: used by A_Lookup, A_Remove method, the key variable is also used by A_Add method *)
    putPosPtr    : POINTER TO T_HashTableEntry := 0;(* Hash table entry position pointer (used by A_Find, A_GetNext, A_GetPrev) *)
    putValue     : ST_BP_ArbInternal;(* Hash table entry value (used by A_AddHead, A_AddTail, A_Find )*)
END_VAR
VAR_OUTPUT
    bOk            : BOOL := FALSE;(* TRUE = Success, FALSE = Failed *)
    getPosPtr    : POINTER TO T_HashTableEntry := 0;(* Returned hash table entry position pointer *)
    getValue     : ST_BP_ArbInternal;(* Returned hash table entry value *)
    nCount        : UDINT := 0;(* Hash table size (number of used entries, used by A_Count) *)
END_VAR
VAR
    {attribute 'pytmc' := '
        pv: Entry
        io: i
     '}
    epicsDataPool    : ARRAY[1..PMPS_PARAM.MAX_ASSERTIONS] OF ST_BP_ArbInternal;(* Structured data element pool for display in EPICS*)
    dataPool    : ARRAY[0..PMPS_PARAM.MAX_ASSERTIONS*3] OF ST_BP_ArbInternal;(* Structured data element pool *)
    entries        : ARRAY[0..PMPS_PARAM.MAX_ASSERTIONS*3] OF T_HashTableEntry;(* Max. number of hash table entries. The value of table entry = 32 bit integer (pointer to dataPool-array-entry) *)
    fbTable     : FB_HashTableCtrl;(* basic hash table control function block *)
    hTable         : T_HHASHTABLE;(* hash table handle *)
    pRefPtr        : POINTER TO ST_BP_ArbInternal := 0;
    indexOfElem    : ULINT;(* Integer value (max. size: x86=>32bit, x64=>64bit)*)
    cstSafeBeam :ST_BeamParams := ( nTran := 0, neVRange := 0, nRate  := 0, nBCRange :=0);// MG
END_VAR
;

END_FUNCTION_BLOCK

ACTION A_Add:
(* Adds entry to the table *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_Add( hTable := hTable, key := key, putValue := ADR(cstSafeBeam)(* we will set this value later *), getPosPtr=>getPosPtr, bOk=>bOk );(* Add new element to the table, getPosPtr points to the new entry *)
IF fbTable.bOk THEN(* Success *)
    fbTable.A_GetIndexAtPosPtr( hTable := hTable, putPosPtr := getPosPtr, getValue =>indexOfElem, bOk=>bOk );(* Get array index of getPosPtr entry *)
    IF fbTable.bOk THEN(* Success *)
        pRefPtr     := ADR( dataPool[indexOfElem] );(* Get pointer to the data element *)

        pRefPtr^ := putValue;(* copy application value *)

        fbTable.A_Add( hTable := hTable, key := key, putValue := pRefPtr, bOk=>bOk );(* Assign the entry value = pointer to the data element *)
        IF fbTable.bOk THEN(* Success *)
            getValue := putValue;
        END_IF
    END_IF
END_IF

DataPoolToEpics();
END_ACTION

ACTION A_Count:
(* Count number of used entries *)
nCount := hTable.nCount;
bOk := TRUE;
END_ACTION

ACTION A_GetFirst:
(* Get first entry position pointer and value *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_GetFirst( hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
    getValue := pRefPtr^;
END_IF
END_ACTION

ACTION A_GetNext:
(* Get next entry position pointer and value *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
bOk := FALSE;

IF putPosPtr = 0 THEN
    RETURN;
END_IF

fbTable.A_GetNext( hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
    getValue := pRefPtr^;
END_IF
END_ACTION

ACTION A_Lookup:
(* Lookup for entry by key  *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_Lookup( key := key, hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
    getValue := pRefPtr^;
END_IF
END_ACTION

ACTION A_Remove:
(* Search for entry and remove it *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_Remove( key := key, hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
    getValue := pRefPtr^;
END_IF

DataPoolToEpics();
END_ACTION

ACTION A_Reset:
(* Reset/initialize linked list *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
bOk := F_CreateHashTableHnd( ADR( entries ), SIZEOF( entries ), hTable );(* Intialize table handle *)
fbTable.A_Reset( hTable := hTable, bOk=>bOk );
nCount := 0;
END_ACTION

ACTION DataPoolToEpics:
MEMCPY(ADR(epicsDataPool), ADR(dataPool), (PMPS_PARAM.MAX_ASSERTIONS) * SIZEOF(ST_BP_ArbInternal));
END_ACTION
Related:

FB_BPControlDevice

(*
Used to request a beam parameter set from EPICS.

Technically just one of these is needed in the entire
PMPS for a line.

*)
FUNCTION_BLOCK FB_BPControlDevice
VAR_INPUT
        (*  Requested pre-optic attenuation %  *)
        {attribute 'pytmc' := 'pv: ReqBP:Transmission
            io: o
            field: HOPR 1;
            field: LOPR 0;
            field: PREC 2;
        '}
        nTran : REAL := PMPS_GVL.TRANS_SCALING_FACTOR;
        (* Pulse-rate *)
        {attribute 'pytmc' := 'pv: ReqBP:Rate
            io: o
            field: EGU Hz
        '}
        nRate : UDINT := 10;
        (* Photon energy ranges *)
        {attribute 'pytmc' := 'pv: ReqBP:PhotonEnergyRanges
            io: o
            field: EGU eV'}
        {attribute 'displaymode' := 'binary'}
        neVRange : DWORD := 4294967295;
        (* Beamclass ranges *)
         {attribute 'pytmc' := 'pv: ReqBP:BeamClassRanges
            io: o'}
        {attribute 'displaymode' := 'binary'}
        nBCRange : WORD := 32767;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
    Arbiter : FB_Arbiter;
END_VAR
VAR
    EpicsReqBP : ST_BeamParams;
    {attribute 'pytmc' := '
        pv: ReqBP:Apply
        io: o
    '}
    bApply : BOOL;

    nControlDeviceID : DWORD;
    sControlDeviceName: STRING:= 'BP Control Device';
    rtApply : R_TRIG;

END_VAR
EpicsReqBP.nTran := nTran;
EpicsReqBP.neVRange := neVRange;
EpicsReqBP.nRate := nRate;
EpicsReqBP.nBCRange := nBCRange;

rtApply(CLK:=bApply);
IF rtApply.Q THEN
    Arbiter.RemoveRequest(nControlDeviceID);
    Arbiter.AddRequest(nControlDeviceID, EpicsReqBP, sControlDeviceName);
    bApply := FALSE;
END_IF

END_FUNCTION_BLOCK

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
    nID : DWORD := PMPS_GVL.EXCLUDED_ASSERTION_ID;
END_VAR
nControlDeviceID:= nID;
END_METHOD
Related:

FB_BPRequestor

(* BP Requestor
Elevates the BP request from an arbiter to PMPS_GVL.stRequestedBP
*)
FUNCTION_BLOCK FB_BPRequestor IMPLEMENTS I_HigherAuthority
VAR_INPUT
END_VAR
VAR_OUTPUT
    q_ReqBP : ST_BeamParams; // Arbitrated BP
END_VAR
VAR_IN_OUT
    Arbiter : FB_Arbiter;
    //FFO : FB_HardwareFFOutput;
END_VAR
VAR
    ReqBP : ST_BeamParams;
    nCohort : ULINT; // Current cohort, inc. 1 cycle after IO is updated
    nRequestCohort : ULINT; // Cohort number recorded at the time of the request.
END_VAR
Arbiter.ElevateRequest(THIS^); // Executes arbitration and retrieves ReqBP

IF nRequestCohort >= nCohort THEN
    // Update requested BP set
    q_ReqBP := ReqBP;
    // Note: we may do more here someday... perhaps all of the output control
    // will reside in here, and be solely controlled by this block. For now
    // I am going to adopt a flat management structure.
    // Start a new cohort
    nCohort := nCohort + 1;
END_IF

END_FUNCTION_BLOCK

// Returns true when outputs to MPS are updated
METHOD CheckRequest : BOOL
VAR_INPUT
    nReqID  : DWORD;
END_VAR
VAR_INST
    xFirstTime : BOOL;
    nId : DWORD;
END_VAR
(* nRequestCohort will be < nCohort after output control
function blocks have been updated to reflect the new request
*)
CheckRequest := nRequestCohort < nCohort;
END_METHOD

METHOD RemoveRequest : BOOL
VAR_INPUT
    nReqID    :    DWORD; //StateID to remove
END_VAR

END_METHOD

METHOD RequestBP : BOOL
VAR_INPUT
    (*StateID of state requesting beam parameter set*)
    nReqID  : DWORD;
    (*Requested beam params*)
    stReqBP : ST_BeamParams;
END_VAR
VAR_INST
    ReqID : DWORD;
    Registered : BOOL := FALSE;
END_VAR
// Check the request is coming from the same source we're used to
IF NOT Registered THEN
    ReqID := nReqID;
    Registered := TRUE;
END_IF
// log a complaint if false
RequestBP := ReqId = nReqId;

// Update internal BP request struct
ReqBP := stReqBP;

// Record current cohort

    nRequestCohort := nCohort;
END_METHOD
Related:

FB_BPTM_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_BPTM_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
VAR CONSTANT
    BPTM_ARR : UDINT := PMPS_PARAM.MAX_ASSERTIONS;
    cCycle : UINT := 20; // How many cycles does it take to complete a BPTM
END_VAR
BPTMBasicFunction();
InterruptAtAllSteps();
FullArbError();

END_FUNCTION_BLOCK

METHOD BPTMBasicFunction
VAR_INPUT
END_VAR
VAR
    //Final and transition assertions
    nTransitionID    :    UDINT := 1;
    stTransitionAssertion    :    ST_BeamParams := (nRate := 10, nBCRange:=0);

    nReqID    :    UDINT    := 2;
    stReqAssertion    :    ST_BeamParams := (nTran := 0.2, nBCRange:=2#0000_0000_0000_0011);

END_VAR
VAR_INST
    fbBPTM_TestBasicFunction    :    BeamParameterTransitionManager;
    fbArbiter    :    FB_Arbiter(1);
    ffo : FB_HardwareFFOutput;
    fbSubSysIO : FB_DummyArbIO;
    xFirstPass    :    BOOL := TRUE;
    eTestStep: E_BPTMTestStates := E_BPTMTestStates.Init;
END_VAR
// Completes arbiter request elevation process
// Necessary for CheckRequest to return true, ever
    fbBPTM_TestBasicFunction(
        fbArbiter := fbArbiter,
        );

fbSubSysIO(LA := fbArbiter, FFO := ffo);

// Note: this struct may also be (over)written to in the tests below, specifically rate.
fbBPTM_TestBasicFunction.stCurrentBeamParameters := fbSubSysIO.q_stSimulatedBPReadback;

//MG: Need to set the Machine mode testing SC
PMPS_GVL.stCurrentBeamParameters.nMachineMode :=0;

CASE eTestStep OF

E_BPTMTestStates.Init:

    TEST('BPTMTest');
    eTestStep := E_BPTMTestStates.WaitingForBeam;

E_BPTMTestStates.WaitingForBeam:
    TEST('BPTM Waits for Beam');

    fbBPTM_TestBasicFunction.i_TransitionAssertionID := nTransitionID;
    fbBPTM_TestBasicFunction.i_stTransitionAssertion := stTransitionAssertion;

    fbBPTM_TestBasicFunction.i_nRequestedAssertionID := nReqID;
    fbBPTM_TestBasicFunction.i_stRequestedAssertion := stReqAssertion;

    IF fbBPTM_TestBasicFunction.eBPTMState = E_BPTMState.WaitForBP AND
        NOT fbBPTM_TestBasicFunction.xEntry THEN

        AssertTrue(fbArbiter.CheckRequestInPool(nTransitionID), 'Arbiter did not accept BPTM transition assertion');

        AssertTrue(fbArbiter.CheckRequestInPool(nReqID), 'Arbiter did not accept BPTM transition assertion');

        AssertFalse(fbBPTM_TestBasicFunction.q_xTransitionAuthorized,
            'Transition should not be authorized until requests are in and beam is ready');

        AssertTrue(fbBPTM_TestBasicFunction.bBusy, 'Busy should be true here');
        AssertFalse(fbBPTM_TestBasicFunction.bDone, 'Done should be false');
        AssertFalse(fbBPTM_TestBasicFunction.bError, 'Error should be false here');

        TEST_FINISHED_NAMED('BPTM Waits for Beam');

        eTestStep := E_BPTMTestStates.Transitioning;
    END_IF


E_BPTMTestStates.Transitioning:
    TEST('BPTM Authorizes Transition');

    fbBPTM_TestBasicFunction.stCurrentBeamParameters.nRate := 0; // satisfies the transition request
    fbBPTM_TestBasicFunction.stCurrentBeamParameters.nBeamClass := 0; // satisfies the transition request

    IF fbBPTM_TestBasicFunction.eBPTMState = E_BPTMState.Transitioning AND
        NOT fbBPTM_TestBasicFunction.xEntry THEN

        AssertTrue(fbArbiter.CheckRequest(nTransitionID),
            'Transition assertion should be in arbiter');

        AssertTrue(fbArbiter.CheckRequest(nReqID),
            'Final assertion should be in arbiter');

        AssertTrue(fbBPTM_TestBasicFunction.q_xTransitionAuthorized,
            'Transition should be authorized at this point.');

        AssertEquals(nReqID, fbBPTM_TestBasicFunction.nCurrentAssertionID,
            'nCurrentAssertionID not set.');

        TEST_FINISHED_NAMED('BPTM Authorizes Transition');

        eTestStep := E_BPTMTestStates.WaitingForFinalAssertion;
    END_IF

E_BPTMTestStates.WaitingForFinalAssertion:
    TEST('BPTM Waits for final BP');

    fbBPTM_TestBasicFunction.i_xDoneMoving := TRUE;

    IF fbBPTM_TestBasicFunction.eBPTMState = E_BPTMState.WaitForFinalBP THEN

        AssertTrue(fbArbiter.CheckRequest(nTransitionID),
        'Transition assertion should be in arbiter');

        TEST_FINISHED_NAMED('BPTM Waits for final BP');

        eTestStep := E_BPTMTestStates.CleaningUp;
    END_IF

E_BPTMTestStates.CleaningUp:

    fbSubSysIO.AutoUpdateBP := TRUE;

    TEST('Cleaning up');

    IF fbBPTM_TestBasicFunction.eBPTMState = E_BPTMState.Done THEN

        AssertFalse(fbArbiter.CheckRequestInPool(nTransitionID), 'Transition req should be removed from arb now');

        eTestStep := E_BPTMTestStates.Done;

        TEST_FINISHED_NAMED('Cleaning up');
    END_IF

E_BPTMTestStates.Done:


    AssertTrue(fbBPTM_TestBasicFunction.bDone, 'Done should be set');
    AssertFalse(fbBPTM_TestBasicFunction.bError, 'Error should be cleared');
    AssertFalse(fbBPTM_TestBasicFunction.bBusy, 'Busy should be false');

    TEST_FINISHED_NAMED('BPTMTest');

END_CASE
END_METHOD

// Simulate error due to full arbiter
METHOD FullArbError
VAR_INPUT
END_VAR
VAR
    //Final and transition assertions
    nTransitionID    :    UDINT := 1000;
    stTransitionAssertion    :    ST_BeamParams :=  ( nTran := 0, neVRange := 0, nRate  := 0);//PMPS_GVL.cstSafeBeam;

    nReqID    :    UDINT    := 2000;
    stReqAssertion    :    ST_BeamParams := ( nTran := 0, neVRange := 0, nRate  := 0);//PMPS_GVL.cstSafeBeam;

    idx : UDINT;

    nRandID : DWORD;
    testReq : ST_BeamParams := (neVRange := 16#FFEE, nRate:=33);

END_VAR
VAR_INST
    fbBPTM_TestFullArb    :    BeamParameterTransitionManager;
    fbArbFullErr    :    FB_Arbiter(1);
    ffo : FB_HardwareFFOutput;
    fbSubSysIO : FB_DummyArbIO;
    fbBPR :  FB_BPRequestor;
    xFirstPass    :    BOOL := TRUE;
    eTestStep: E_BPTMTestStates := E_BPTMTestStates.Init;
    tonRetryTimeout : TON := (PT:=T#5s);
    entryToRetry : bool := true;
    fbRand : DRAND := (Seed :=1);
END_VAR
CASE eTestStep OF

E_BPTMTestStates.Init:

    TEST('BPTMErrorFullArb');

    // Fill the arbiter
    FOR idx := 1 TO PMPS_PARAM.MAX_ASSERTIONS // this fills the arbiter
                    - fbBPTM_TestFullArb.cReqArbCapacity + 1 DO // but test just one over the allowed amount
        fbArbFullErr.AddRequest(nReqID := idx, stReqBP := testReq, sDevName :='Device');
    END_FOR

    fbSubSysIO.AutoUpdateBP := TRUE;

    AssertTrue(PMPS_PARAM.MAX_ASSERTIONS - fbArbFullErr.nEntryCount < fbBPTM_TestFullArb.cReqArbCapacity,
        'Arbiter is not full enough, rest of tests will be invalid');

    eTestStep := E_BPTMTestStates.Error;

E_BPTMTestStates.Error:

    TEST('BPTM Full Error');

    IF fbBPTM_TestFullArb.bError THEN

        AssertTrue(fbBPTM_TestFullArb.bError AND fbBPTM_TestFullArb.nErrId = PMPS_CODES.NoRoomInArb,
            'Incorrect error response from full arbiter.');

        TEST_FINISHED_NAMED('BPTM Full Error');

        eTestStep := E_BPTMTestStates.Retry;

    END_IF

E_BPTMTestStates.Retry:

    TEST('Test Retry Post Failed Final');

    // Clear the arbiter
    if entryToRetry THEN
        FOR idx := 1 TO PMPS_PARAM.MAX_ASSERTIONS + 1 DO
            fbArbFullErr.RemoveRequest(idx);
        END_FOR
        entryToRetry := FALSE;
    END_IF

    // Push the retry button
    fbBPTM_TestFullArb.bRetry := TRUE;

    tonRetryTimeout(IN:=TRUE);

    IF tonRetryTimeout.Q OR fbBPTM_TestFullArb.q_xTransitionAuthorized THEN
        AssertTrue(fbBPTM_TestFullArb.q_xTransitionAuthorized, 'Transition should have been authorized by now');
        AssertTrue(fbArbFullErr.CheckRequest(nTransitionID), 'Transition ID is missing from arbiter while transition is authorized');
        AssertTrue(fbArbFullErr.CheckRequest(nReqID), 'Final ID is missing from arbiter while transition is authorized');

        TEST_FINISHED_NAMED('Test Retry Post Failed Final');

        eTestStep := E_BPTMTestStates.CleaningUp;
    END_IF

E_BPTMTestStates.CleaningUp:
    TEST_FINISHED_NAMED('BPTMErrorFullArb');

END_CASE

fbBPTM_TestFullArb(
    fbArbiter := fbArbFullErr,
    i_TransitionAssertionID := nTransitionID,
    i_stTransitionAssertion := stTransitionAssertion,

    i_nRequestedAssertionID := nReqID,
    i_stRequestedAssertion := stReqAssertion,

    stCurrentBeamParameters := fbSubSysIO.q_stSimulatedBPReadback,
);

fbSubSysIO(LA := fbArbFullErr, FFO := ffo);
END_METHOD

// Test the BPTM seamlessly handles new requests at any stage
// Verify old requests are removed from arbiter
{attribute 'no_check'}
METHOD InterruptAtAllSteps
VAR_INPUT
END_VAR
VAR
    //Final and transition assertions
    nTransitionID    :    UDINT := 16#DAAD;
    stTranReq    :    ST_BeamParams := PMPS_GVL.cst0RateBeam;

    nFirstReqID    :    UDINT    := 16#FEED;
    stFirstReq    :    ST_BeamParams := (nTran :=1, nRate := 16#FEED, neVRange :=2#1111_1111_1111_1111_1111_1111_1111_1111);

END_VAR
VAR_INST
    fbBPTM_InterruptionTest    :    BeamParameterTransitionManager;

    ffo : FB_HardwareFFOutput := (bAutoReset:=TRUE);
    fbSubSysIO : FB_DummyArbIO;
    fbBPR :  FB_BPRequestor;
    xFirstPass    :    BOOL := TRUE;
    eTestStep: E_BPTMTestStates := E_BPTMTestStates.Init;
    tonRetryTimeout : TON := (PT:=T#5s);

    fbBPTMDiscCycles : BeamParameterTransitionManager;
    fbSubSysIODisc : FB_DummyArbIO;
    fbArbDisc : FB_Arbiter(1000);

    iCount : UINT;

    aBPTM : ARRAY [1..cCycle] OF BeamParameterTransitionManager;
    BPTMUnderTest : REFERENCE TO BeamParameterTransitionManager;
    fbArbiter    :    FB_Arbiter(1);
    aArbIO : ARRAY [1..cCycle] OF FB_DummyArbIO;
    ArbIO : REFERENCE TO FB_DummyArbIO;
    nCycleLim : UINT;
    nCycle : UINT;
    sCurrentTestName : STRING :='';
    bCycleLimHit : BOOL;
    nAllStepsDone : BYTE;
    tTimeOut : TON := (PT:=T#20ms);

    bTestedOnce : BOOl;
END_VAR
VAR CONSTANT
    nNewTarget : INT := 0;
    nRequestBP : INT := 1;
    nWaitForBP : INT := 2;
    nTransitioning : INT := 3;
    nWaitForFinalBP : INT := 4;
    nCleaningUp : INT := 5;
    nDone : INT := 6;
    nIdle : INT := 7;
END_VAR
(* These tests will confirm the BPTM is interruptable at any step,
meaning it will not deadlock or stall, and will always complete with
the correct sequencing *)

(* This functionality is crucial as a deadlocked BPTM will need manual intervention,
users of the BPTM should always be able to just set a target BP that is easy to reach
at any point in time, and have the BPTM head in that direction and succeed. *)

// Determine how many cycles it would take normally, to complete a full BPTM cycle
// This count will be verified against the current count constant

IF NOT bTestedOnce THEN

TEST('CheckBPTM Cycle Count');

WHILE fbBPTMDiscCycles.eBPTMState <> E_BPTMState.Idle DO

    fbBPTMDiscCycles.i_TransitionAssertionID := nTransitionID;
    fbBPTMDiscCycles.i_stTransitionAssertion := stTranReq;

    fbBPTMDiscCycles.i_nRequestedAssertionID := nFirstReqID;
    fbBPTMDiscCycles.i_stRequestedAssertion := stFirstReq;

    fbBPTMDiscCycles(fbArbiter:=fbArbDisc);
    fbSubSysIODisc(LA:=fbArbDisc, FFO:=ffo);

    fbBPTMDiscCycles.stCurrentBeamParameters := fbSubSysIODisc.q_stSimulatedBPReadback;

    fbSubSysIODisc.AutoUpdateBP := TRUE;

    fbBPTMDiscCycles.i_xDoneMoving S= fbBPTMDiscCycles.q_xTransitionAuthorized;

    iCount := iCount + 1;
END_WHILE

IF fbBPTMDiscCycles.eBPTMState = E_BPTMState.Idle THEN
    AssertTrue(cCycle >= iCount, CONCAT('cCycle is too low, increase it to', INT_TO_STRING(iCount)));
    TEST_FINISHED_NAMED('CheckBPTM Cycle Count');
END_IF

// Run BPTM for nCycleLim cycles, switch to a new target, verify BPTM completes anyways

FOR nCycleLim := 1 TO cCycle DO

    nCycle := 0;

    bCycleLimHit := FALSE;

    BPTMUnderTest REF= aBPTM[nCycleLim];
    ArbIO REF= aArbIO[nCycleLim];

    BPTMUnderTest.i_TransitionAssertionID := nTransitionID;
    BPTMUnderTest.i_stTransitionAssertion := stTranReq;

    ArbIO.AutoUpdateBP := TRUE;

    // Start the test for this Cycle Limit
    sCurrentTestName := CONCAT('CycleLimTest:',INT_TO_STRING(nCycleLim));
    TEST(sCurrentTestName);

    nAllStepsDone := 0;

    tTimeOut(IN:=FALSE);

    WHILE nAllStepsDone <> 16#FF DO

        tTimeOut(IN:= TRUE);

        IF tTimeOut.Q THEN
            AssertTrue(FALSE, CONCAT('Failed ', BYTE_TO_HEXSTR(nAllStepsDone, 2, FALSE)));
            EXIT;
        END_IF

        // Verify all steps complete
        IF bCycleLimHit THEN
            nAllStepsDone.nNewTarget S= BPTMUnderTest.eBPTMState = E_BPTMState.NewTarget;
            nAllStepsDone.nRequestBP S= BPTMUnderTest.eBPTMState = E_BPTMState.RequestBP;
            nAllStepsDone.nWaitForBP S= BPTMUnderTest.eBPTMState = E_BPTMState.WaitForBP;
            nAllStepsDone.nTransitioning S= BPTMUnderTest.eBPTMState = E_BPTMState.Transitioning;
            nAllStepsDone.nWaitForFinalBP S= BPTMUnderTest.eBPTMState = E_BPTMState.WaitForFinalBP;
            nAllStepsDone.nCleaningUp S= BPTMUnderTest.eBPTMState = E_BPTMState.CleaningUp;
            nAllStepsDone.nDone S= BPTMUnderTest.eBPTMState = E_BPTMState.Done;
            nAllStepsDone.nIdle S= BPTMUnderTest.eBPTMState = E_BPTMState.Idle;
        ELSE
            nAllStepsDone := 0;
        END_IF

        // After nCycleLim cycles (nCycle), change the target
        IF nCycle >= nCycleLim THEN
            BPTMUnderTest.i_nRequestedAssertionID := nCycleLim;
            BPTMUnderTest.i_stRequestedAssertion.nRate := nCycleLim;
            bCycleLimHit := TRUE;
        ELSE
            BPTMUnderTest.i_nRequestedAssertionID := nFirstReqID;
            BPTMUnderTest.i_stRequestedAssertion := stFirstReq;
        END_IF

        BPTMUnderTest(fbArbiter:=fbArbiter);
        ArbIO(LA:=fbArbiter, FFO := ffo);

        BPTMUnderTest.stCurrentBeamParameters := ArbIO.q_stSimulatedBPReadback;

        BPTMUnderTest.i_xDoneMoving := BPTMUnderTest.q_xTransitionAuthorized;

        nCycle := nCycle + 1;

    END_WHILE

    AssertTrue(fbArbiter.CheckRequestInPool(BPTMUnderTest.i_nRequestedAssertionID),
        CONCAT('Final req not in pool: ', UINT_TO_STRING(nCycleLim)));
    AssertFalse(fbArbiter.CheckRequestInPool(BPTMUnderTest.i_TransitionAssertionID),
        'TransReq still in pool');
    AssertFalse(fbArbiter.CheckRequestInPool(nFirstReqID),
        'First req still in pool');
    AssertTrue(bCycleLimHit,
        CONCAT('Cycle lim not hit', UINT_TO_STRING(nCycleLim)));
    AssertTrue(ArbIO.q_stSimulatedBPReadback.nRate = BPTMUnderTest.i_stRequestedAssertion.nRate,
        'Rates are not equal...');

    TEST_FINISHED_NAMED(sCurrentTestName);

    // Clean up for next test

    fbArbiter.RemoveRequest(nFirstReqID);
    fbArbiter.RemoveRequest(nTransitionID);
    fbArbiter.RemoveRequest(nCycleLim);

END_FOR

END_IF
END_METHOD

// Test the BPTM seamlessly handles new requests at any stage
// Verify old requests are removed from arbiter
METHOD InterruptedTransition
VAR_INPUT
END_VAR
VAR
    //Final and transition assertions
    nTransitionID    :    UDINT := 3;
    stTranReq    :    ST_BeamParams := PMPS_GVL.cstSafeBeam;

    nFirstReqID    :    UDINT    := 1;
    stFirstReq    :    ST_BeamParams := (nRate := 100);

    nSecondReqID    :    UDINT    := 2;
    stSecReq    :    ST_BeamParams := (nRate:= 200);

END_VAR
VAR_INST
    fbBPTM_InterruptionTest    :    BeamParameterTransitionManager;
    fbArbiter    :    FB_Arbiter(1);
    ffo : FB_HardwareFFOutput;
    fbSubSysIO : FB_DummyArbIO;
    fbBPR :  FB_BPRequestor;
    xFirstPass    :    BOOL := TRUE;
    eTestStep: E_BPTMTestStates := E_BPTMTestStates.Init;
    tonRetryTimeout : TON := (PT:=T#5s);
END_VAR
CASE eTestStep OF

E_BPTMTestStates.Init:

    TEST('BPTM Intr Trans');

    fbBPTM_InterruptionTest.i_TransitionAssertionID := nTransitionID;
    fbBPTM_InterruptionTest.i_stTransitionAssertion := stTranReq;

    fbBPTM_InterruptionTest.i_nRequestedAssertionID := nFirstReqID;
    fbBPTM_InterruptionTest.i_stRequestedAssertion := stFirstReq;

    fbSubSysIO.AutoUpdateBP := TRUE; // Enable auto update

    eTestStep := E_BPTMTestStates.WaitingForTransitionAssertion;

E_BPTMTestStates.WaitingForBeam:

    IF fbBPTM_InterruptionTest.eBPTMState = E_BPTMState.WaitingForTransitionAssertion AND
        NOT fbBPTM_InterruptionTest.xEntry THEN // since we're not in entry anymore

        //change the request to interrupt
        fbBPTM_InterruptionTest.i_nRequestedAssertionID := nSecondReqID;
        fbBPTM_InterruptionTest.i_stRequestedAssertion := stSecReq;

        eTestStep := E_BPTMTestStates.Transitioning;
    END_IF

E_BPTMTestStates.Transitioning:

    TEST('New Target Gets To Trans Auth');

    tonRetryTimeout(IN:=TRUE);

    IF tonRetryTimeout.Q OR fbBPTM_InterruptionTest.q_xTransitionAuthorized THEN
        AssertTrue(fbBPTM_InterruptionTest.q_xTransitionAuthorized, 'Transition should have been authorized by now');
        AssertTrue(fbArbiter.CheckRequest(nTransitionID), 'Transition ID is missing from arbiter while transition is authorized');
        AssertTrue(fbArbiter.CheckRequest(nSecondReqID), 'Final ID is missing from arbiter while transition is authorized');

        TEST_FINISHED_NAMED('New Target Gets To Trans Auth');

        eTestStep := E_BPTMTestStates.CleaningUp;
    END_IF

E_BPTMTestStates.CleaningUp:
    TEST_FINISHED_NAMED('BPTM Intr Trans');

END_CASE

fbBPTM_InterruptionTest(
    fbArbiter := fbArbiter,
    stCurrentBeamParameters := fbSubSysIO.q_stSimulatedBPReadback
);

fbSubSysIO(LA := fbArbiter, FFO := ffo);
END_METHOD
Related:

FB_CTLS_Outputs

(*
Controls auxiliary beam class outputs.

1-3 are mapped to control the Cu beamline rate via the LCLS II MPS.

Maps beam rate requests to 1, 10, or full rate beam for Cu linac.

*)
FUNCTION_BLOCK FB_CTLS_Outputs
VAR_INPUT
    BP    :    ST_BeamParams;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    {attribute 'pytmc' := 'pv: CTLSChannel
        io: i
        field: DESC CTLS Rate Control hardwire channel state'}
    epicsBitmap : WORD;

    {attribute 'TcLinkTo' := '[1] := TIIB[PMPS_Premp]^Channel 9^Output;
                              [2] := TIIB[PMPS_Premp]^Channel 10^Output;
                              [3] := TIIB[PMPS_Premp]^Channel 11^Output;'}
    q_CLTS_ASSERTION_LINES    AT    %Q*    : ARRAY [1..3] OF    BOOL;



END_VAR
// CLTS 1Hz
q_CLTS_ASSERTION_LINES[1] := BP.nRate >= 1;
// CLTS 10Hz
q_CLTS_ASSERTION_LINES[2] := BP.nRate >= 10;
// CLTS Full rate
q_CLTS_ASSERTION_LINES[3] := BP.nRate >= 120;


epicsBitMap.0 := q_CLTS_ASSERTION_LINES[1];
epicsBitMap.1 := q_CLTS_ASSERTION_LINES[2];
epicsBitMap.2 := q_CLTS_ASSERTION_LINES[3];

END_FUNCTION_BLOCK
Related:

FB_DiffBP_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_DiffBP_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR

END_VAR
BPDiff();

END_FUNCTION_BLOCK

//Verify BP comparison remains logical.
METHOD BPDiff
VAR_INPUT
END_VAR
VAR
    BP1    :    ST_BeamParams;
    BP2    :    ST_BeamParams;
END_VAR
TEST('BPDiffCheck');

BP2.nTran := 10;

AssertTrue(
    F_DifferentBeamParams(BP1, BP2),
    'DifferentBP eval is broken (True)');

BP1.nTran := BP2.nTran;

AssertFalse(
    F_DifferentBeamParams(BP2, BP1),
    'DifferentBP eval is broken (False)');

TEST_FINISHED();
END_METHOD
Related:

FB_DummyArbIO

(* Use to test other library integrations of PMPS preemptive functionality *)
(* This block stands in for the FB_SubSysToArb_IO block. *)

FUNCTION_BLOCK FB_DummyArbIO IMPLEMENTS I_HigherAuthority, I_LowerAuthority
VAR_INPUT
    //stCurrentBP : REFERENCE TO ST_BeamParams; //Set to something to redirect currentBP update
    LA : I_LowerAuthority;

    tRateDelay : TIME; // Delay until rate is looped back into q_stSimulatedBPReadback
    tTransDelay : TIME; // Delay until transmission is looped back into q_stSimulatedBPReadback
    tPEDelay : TIME; // Delay until PE is looped back into q_stSimulatedBPReadback
    tAptDelay : TIME; // Delay until apertures are looped back into q_stSimulatedBPReadback
    tSattDelay : TIME; // Delay until apertures are looped back into q_stSimulatedBPReadback

END_VAR
VAR_OUTPUT
    q_stSimulatedBPReadback : ST_BeamParams; // Updated to
END_VAR
VAR_IN_OUT
    FFO : FB_HardwareFFOutput;
END_VAR
VAR
   bAutoUpdateBP : BOOL := FALSE; // Set by AutoUpdateBP property, if true causes this FB to update PMPS_GVL.stCurrentBP to the
   // requested value automatically within the RequestBP method and restore it within the RemoveRequest method
   stNewReqBP : ST_BeamParams;

   stReqOutBP : ST_BeamParams;// To be written on next ApplyBPReq call

   stSaveCurrentBP : ST_BeamParams; // Will be restored after RemoveRequest then ApplyBPReq call

   nSavedID : DWORD; // Used to simulate CheckRequest method

   tonRate : TON;
   tonTrans : TON;
   tonPE : TON;
   tonApt : TON;
   tonSatt : TON;
   tonBCRange: TON;
   tonBC :TON;

END_VAR
LA.ElevateRequest(THIS^);

ApplyBPReq(FALSE); //If autoupdate is set, this will propagate the request to PMPS_GVL.stCurrentBP;

END_FUNCTION_BLOCK

// Call this method to update q_stSimulatedBPReadback at will, used if auto-update is set to false
METHOD ApplyBPReq
VAR_INPUT
    bUpdateBP : BOOL := FALSE; // Set true and call this method to set q_stSimulatedBPReadback to the request
END_VAR
VAR
    BP : REFERENCE TO ST_BeamParams;
END_VAR
IF bUpdateBP THEN
    stSaveCurrentBP := q_stSimulatedBPReadback;
    q_stSimulatedBPReadback := stReqOutBP;
ELSIF bAutoUpdateBP THEN
    tonApt.IN := 0 <> MEMCMP(ADR(stReqOutBP.astApertures), ADR(q_stSimulatedBPReadback.astApertures), SIZEOF(q_stSimulatedBPReadback.astApertures));
    tonApt(PT:=tAptDelay);
    IF tonApt.Q THEN q_stSimulatedBPReadback.astApertures := stReqOutBP.astApertures; END_IF

    tonRate(IN:=stReqOutBP.nRate<>q_stSimulatedBPReadback.nRate, PT:=tRateDelay);
    IF tonRate.Q THEN q_stSimulatedBPReadback.nRate := stReqOutBP.nRate; END_IF

    tonTrans(IN:=stReqOutBP.nTran<>q_stSimulatedBPReadback.nTran, PT:=tTransDelay);
    IF tonTrans.Q THEN q_stSimulatedBPReadback.nTran := stReqOutBP.nTran; END_IF

    tonPE(IN:=stReqOutBP.neVRange<>q_stSimulatedBPReadback.neVRange, PT:=tPEDelay);
    IF tonPE.Q THEN
        IF stReqOutBP.neVRange = 0 THEN
            q_stSimulatedBPReadback.neVRange := 0;
            q_stSimulatedBPReadback.neVRange.0 := TRUE;
        ELSE
            q_stSimulatedBPReadback.neVRange := stReqOutBP.neVRange;
        END_IF
    END_IF

    tonBCRange(IN:=stReqOutBP.nBCRange<>q_stSimulatedBPReadback.nBCRange, PT:=tPEDelay);
     IF tonBCRange.Q THEN
        IF stReqOutBP.nBCRange = 0 THEN
            q_stSimulatedBPReadback.nBCRange := 0;
        ELSE
            q_stSimulatedBPReadback.nBCRange := stReqOutBP.nBCRange;
        END_IF
    END_IF

    tonSatt.IN := 0 <> MEMCMP(ADR(stReqOutBP.astAttenuators), ADR(q_stSimulatedBPReadback.astAttenuators), SIZEOF(q_stSimulatedBPReadback.astAttenuators));
    tonSatt(PT:=tSattDelay);
    IF tonSatt.Q THEN q_stSimulatedBPReadback.astAttenuators := stReqOutBP.astAttenuators; END_IF
END_IF

ffo.ExecuteNoLog();

IF NOT ffo.q_xFastFaultOut THEN q_stSimulatedBPReadback.nRate := 0; END_IF
END_METHOD

METHOD CheckRequest : BOOL
VAR_INPUT
    nReqID  : DWORD;
END_VAR
CheckRequest := nReqID = nSavedID AND (nReqID <> 0) AND (nReqID <> PMPS_GVL.EXCLUDED_ASSERTION_ID);
END_METHOD

// <Arbiter Internal>
// Elevates the arbitrated BP set to something above.
// Could be another arbiter, or a BP requester/ IO,
// or an FB that locks in a specific portion of the BP set.
METHOD ElevateRequest : BOOL
VAR_INPUT
    HigherAuthority : I_HigherAuthority;
END_VAR

END_METHOD

METHOD RemoveRequest : BOOL
VAR_INPUT
    (*StateID to remove*)
    nReqID  : DWORD;
END_VAR
nSavedID := 0;

stReqOutBP := stSaveCurrentBP;

RemoveRequest := TRUE;
END_METHOD

// Request a BP from this higher authority
METHOD RequestBP : BOOL
VAR_INPUT
    (*StateID of state requesting beam parameter set*)
    nReqID  : DWORD;
    (*Requested beam params*)
    stReqBP : ST_BeamParams;
END_VAR
IF nReqID <> 0 AND nReqID <> PMPS_GVL.EXCLUDED_ASSERTION_ID THEN
    nSavedID := nReqID;
    stReqOutBP := stReqBP;

    RequestBP := TRUE;
ELSE
    RequestBP := FALSE;
END_IF
END_METHOD

PROPERTY AutoUpdateBP : BOOL
VAR
END_VAR
AutoUpdateBP := bAutoUpdateBP;
END_PROPERTY

PROPERTY AutoUpdateBP : BOOL
VAR
END_VAR
bAutoUpdateBP := AutoUpdateBP;
END_PROPERTY

PROPERTY nLowerAuthorityID : DWORD
VAR
END_VAR
nLowerAuthorityID := PMPS_GVL.EXCLUDED_ASSERTION_ID;
END_PROPERTY
Related:

FB_DummyHA

FUNCTION_BLOCK FB_DummyHA IMPLEMENTS I_HigherAuthority, I_LowerAuthority
VAR_INPUT
    ReqAcknowledged : BOOL := TRUE; // Use to control when the HA CHeckRequest Returns TRUE
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR


END_FUNCTION_BLOCK

METHOD CheckRequest : BOOL
VAR_INPUT
    nReqID  : DWORD;
END_VAR
CheckRequest := ReqAcknowledged;
END_METHOD

// <Arbiter Internal>
// Elevates the arbitrated BP set to something above.
// Could be another arbiter, or a BP requester/ IO,
// or an FB that locks in a specific portion of the BP set.
METHOD ElevateRequest : BOOL
VAR_INPUT
    HigherAuthority : I_HigherAuthority;
END_VAR

END_METHOD

METHOD RemoveRequest : BOOL
VAR_INPUT
    (*StateID to remove*)
    nReqID  : DWORD;
END_VAR
RemoveRequest := TRUE;
END_METHOD

METHOD RequestBP : BOOL
VAR_INPUT
    (*StateID of state requesting beam parameter set*)
    nReqID  : DWORD;
    (*Requested beam params*)
    stReqBP : ST_BeamParams;
END_VAR
RequestBP := TRUE;
END_METHOD

PROPERTY nLowerAuthorityID : DWORD
VAR
END_VAR
nLowerAuthorityID := PMPS_GVL.EXCLUDED_ASSERTION_ID;
END_PROPERTY
Related:

FB_evRangeCalculator_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_evRangeCalculator_Test EXTENDS FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
VAR CONSTANT
    RandomBoundary : INT := 2;
END_VAR
BasicFunction();

END_FUNCTION_BLOCK

{attribute 'no_check'}
METHOD BasicFunction
VAR_INPUT
END_VAR
VAR
    {attribute 'displaymode' := 'binary'}
    LastWord : DWORD := 2#0000_0000_0000_0000_0000_0000_0000_0010;
    {attribute 'displaymode' := 'binary'}
    Result : DWORD;
    {attribute 'displaymode' := 'binary'}
    TestEV : DWORD;

    {attribute 'displaymode' := 'binary'}
    tEvW : DWORD;
    tEv : REAL;


    fbStr : FB_FormatString;

    MiscNumber : INT;
END_VAR
VAR CONSTANT
    nLowerLimitBoundary : INT := 0;

    RandomBoundary : INT := 2;
    MNU : INT := 3; //RandomBoundary + 1
    MND : INT := 1; //RandomBoundary - 1
END_VAR
Test('CheckRanges');

//Lower limit equal
tEvW.nLowerLimitBoundary := TRUE;
tEv := PMPS_GVL.g_areVBoundaries[nLowerLimitBoundary];
Result := F_eVRangeCalculator(tEv, 0);
AssertTrue(
    Result = tEvW,
    'Lower limit not inclusive');

// Lower range flags
tEvW.nLowerLimitBoundary := TRUE;
tEv := PMPS_GVL.g_areVBoundaries[nLowerLimitBoundary] / 2;
Result := F_eVRangeCalculator(tEv, 0);
AssertTrue(
    Result = tEvW,
    'eV below lower range should come back true');

//Mid range
tEvW := 0;
tEvW.RandomBoundary := TRUE;
//ev result that is smack dab in the middle of a range
tEv := PMPS_GVL.g_areVBoundaries[RandomBoundary] - ((PMPS_GVL.g_areVBoundaries[RandomBoundary] - PMPS_GVL.g_areVBoundaries[MND]) / 2);
Result := F_eVRangeCalculator(tEv, 0);
AssertTrue(
    Result = tEvW,
    'In range failed, whatever you did, it broke this really bad.');

//Out of range
tEvW := 0;
tEvW.RandomBoundary := TRUE;
//Ev result that is beyond the current range (midway into the next range up)
tEv := PMPS_GVL.g_areVBoundaries[MNU] - (PMPS_GVL.g_areVBoundaries[MNU]-PMPS_GVL.g_areVBoundaries[RandomBoundary])/2;
Result := F_eVRangeCalculator(tEv, 0);
AssertFalse(
    Result = tEvW,
    'Out of range failed, whatever you did, it broke this really bad.');

TEST_FINISHED();

Test('Negative eV failsafes');
//Negative eV
AssertTrue(
    F_eVRangeCalculator(-300, 0) = 16#FFFF_FFFF,
    'Working with antimatter? Negative eV should failsafe to FFFF.');

TEST_FINISHED();

Test('Out of range eV failsafes');
// eV too high
AssertTrue(
    F_eVRangeCalculator(PMPS_GVL.g_areVBoundaries[PMPS_GVL.g_cBoundaries] + 1, 0) = 16#FFFF_FFFF,
    'eV above the last threshold should failsafe.');

TEST_FINISHED();

Test('Check hyst keeps');

LastWord := F_eVRangeCalculator(PMPS_GVL.g_areVBoundaries[RandomBoundary], 2#0000_0000_0000_0000_0000_0000_0000_0000);

// Last word should be 2#0000_0000_0000_0100

    Result := F_eVRangeCalculator(
        PMPS_GVL.g_areVBoundaries[RandomBoundary] + PMPS_GVL.reVHyst - PMPS_GVL.reVHyst/2,
         LastWord);
    TestEV := 0;
    TestEV.RandomBoundary := TRUE;
    TestEV.MNU := True;

    fbStr.arg1 := F_DWORD(Result);
    fbStr.arg2 := F_DWORD(TestEV);
    fbStr.sFormat := 'Moving up does not stick. Result: %b vs. Test: %b';
    fbStr();

AssertTrue(Result = TestEV,
    fbStr.sOut);

    Result := F_eVRangeCalculator(
        PMPS_GVL.g_areVBoundaries[RandomBoundary - 1] - PMPS_GVL.reVHyst + PMPS_GVL.reVHyst/2,
        LastWord);
    TestEV := 0;
    TestEV.RandomBoundary := TRUE;
    TestEV.MND := True;

    fbStr.arg1 := F_DWORD(Result);
    fbStr.arg2 := F_DWORD(TestEV);
    fbStr.sFormat := 'Moving down does not stick. Result: %b vs. Test: %b';
    fbStr();

AssertTrue(Result = TestEV,
    fbStr.sOut);

TEST_FINISHED();

Test('Check hyst drops');

    Result := F_eVRangeCalculator(
        PMPS_GVL.g_areVBoundaries[MND] - PMPS_GVL.reVHyst*1.1,
        LastWord);
    TestEV := 0;
    TestEV.MND := TRUE;

    fbStr.arg1 := F_DWORD(Result);
    fbStr.arg2 := F_DWORD(TestEV);
    fbStr.sFormat := 'Moving down does not drop. Result: %b vs. Test: %b';
    fbStr();

AssertTrue(Result = TestEV,
    fbStr.sOut);

    Result := F_eVRangeCalculator(
        PMPS_GVL.g_areVBoundaries[RandomBoundary] + PMPS_GVL.reVHyst*1.1,
        LastWord);
    TestEV := 0;
    TestEV.MNU := TRUE;

    fbStr.arg1 := F_DWORD(Result);
    fbStr.arg2 := F_DWORD(TestEV);
    fbStr.sFormat := 'Moving up does not drop. Result: %b vs. Test: %b';
    fbStr();

AssertTrue(Result = TestEV,
    fbStr.sOut);

TEST_FINISHED();
END_METHOD
Related:

FB_eVSimulator

(* eV Simulator
A. Wallace 2019-8-30

Adds noise to the eV and changes eV position occasionally.
*)
FUNCTION_BLOCK FB_eVSimulator
VAR_INPUT
    NoiseLevel      :       REAL    := 0; // eV Noise
    ChangeTime : TIME := T#10S;
END_VAR
VAR_OUTPUT
    eV      :       REAL := 300;
END_VAR
VAR
    eVRange : REAL := 1600;
    timer: TON;
    eVRand  :       DRAND :=(Seed:=0);
    NoiseRand : DRAND := (Seed:=0);
END_VAR
timer(in:=TRUE, PT:=ChangeTime);

//Occasionally change eV
IF timer.Q THEN
    timer(in:=FALSE);
    eVRand();
    eV := LIMIT(0, eVRange*LREAL_TO_REAL(eVRand.Num), eVRange);
END_IF

// Noise generation
NoiseRand();
eV := eV + NoiseLevel*LREAL_TO_REAL(NoiseRand.Num);
NoiseRand();
eV := LIMIT(0, ev - NoiseLevel*LREAL_TO_REAL(NoiseRand.Num), eVRange);

END_FUNCTION_BLOCK

FB_eVWithinSpec_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_eVWithinSpec_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
evWithinRangeChecks();

END_FUNCTION_BLOCK

METHOD evWithinRangeChecks
VAR_INPUT
END_VAR
VAR

END_VAR
TEST('evWithinRangeChecks');

//Upper limit equal
AssertTrue(
    F_eVWithinSpec(300, 2#0000_0000_0000_0010),
    'Upper limit not inclusive');
//Lower limit equal
AssertTrue(
    F_eVWithinSpec(300, 2#0000_0000_0000_0100),
    'Lower limit not inclusive');
//Out of range
AssertFalse(
    F_eVWithinSpec(300.1, 2#0000_0000_0000_0010),
    'Out of range failed, whatever you did, it broke this really bad.');
//Negative eV
AssertFalse(
    F_eVWithinSpec(-300, 16#FFFF),
    'Working with antimatter? Negative eV should not be acceptable.');
//Upper unallocated range
AssertFalse(
    F_eVWithinSpec(1701, 2#1000_0000_0000_0000),
    'eV range should not evaluate past 1.7keV');

TEST_FINISHED();
END_METHOD
Related:

FB_FastFault

(* Fast Fault
2019-9-13 A. Wallace

Use this block to generate a beam-off fault. Connects to a fast fault hardware output
function block to contribute to the state of the fast fault output (FFO).

If the i_xOK goes false, the associated FFO will go false, despite the state of any other
contributing fast faults, unless the FFO is currently vetoed.

*)
{attribute 'reflection'}
FUNCTION_BLOCK FB_FastFault
VAR_INPUT

    i_xOK        :    BOOL; // Connect to fast-fault condition (false produces fault)
    i_xReset    :    BOOL; // Resets when i_xOK is true and this is true
    i_xAutoReset : BOOL := FALSE; // Automatically clear fast fault (latching vs non-latching)
    i_xVetoable : BOOL := TRUE; // Mask this fast fault if the FFO veto device is true

    i_DevName : T_MaxString := ''; // Device name for diagnostic
    i_Desc : T_MaxString := ''; // Description of fast fault (you should set at init)
    i_TypeCode : UINT; // Error code for classifying fast faults
END_VAR
VAR_OUTPUT
    o_xFFLine    :    BOOL; //Connect to HW output or another FF input if you like (Optional)
END_VAR
VAR_IN_OUT
    io_fbFFHWO        :    FB_HardwareFFOutput; //Point to FB_HardwareFFOutput of your choice
END_VAR
VAR
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath    :    T_MaxString;

    FFInfo : ST_FFInfo;

    RegistrationIdx : UINT := 1; // The index this FF was registered in the FFO

    xInit    :    BOOL :=TRUE;

    InfoStringFmtr : FB_FormatString;

    InUse : T_MaxString;
    AutoReset : T_MaxString;

END_VAR
IF xInit    THEN
    FFInfo.sPath := sPath;
    FFInfo.InUse := True;
    FFInfo.TypeCode := i_TypeCode;
    FFInfo.DevName := i_DevName;
    FFInfo.Desc := i_Desc;
    FFInfo.AutoReset := i_xAutoReset;
    FFInfo.Vetoable := i_xVetoable;

    InUse := BOOL_TO_STRING(FFInfo.InUse);
    AutoReset := BOOL_TO_STRING(FFInfo.AutoReset);
    InfoStringFmtr(sFormat:='%s;%s;%X;%s;%s;%s;',
        arg1 := F_STRING(FFinfo.sPath),
        arg2 := F_STRING(InUse),
        arg3 := F_WORD(FFInfo.TypeCode),
        arg4 := F_STRING(FFInfo.DevName),
        arg5 := F_STRING(FFInfo.Desc),
        arg6 := F_STRING(AutoReset),
        sOut=>FFInfo.InfoString);
    io_fbFFHWO.Register(
        stFFInfo:=FFInfo,
        Idx=>RegistrationIdx);
    //<TODO> if registration doesn't succeed, send warning to diagnostic
    xInit := FALSE;
END_IF

io_fbFFHWO.IdxCheckIn(Idx:=RegistrationIdx, OK := i_xOK, Reset:= i_xReset);

END_FUNCTION_BLOCK
Related:

FB_FastFault_Test

{attribute 'call_after_init'}
{attribute 'no_check'}
FUNCTION_BLOCK FB_FastFault_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    fbTime : FB_LocalSystemTime := ( bEnable := TRUE, dwCycle := 1 ); //Get current system time, used for override
END_VAR
VAR CONSTANT
    OvrdTime : TIME := T#2s;
END_VAR
FFCombinedFunctionality();
FFRegistration();
FFOvrdAndNextFault();

END_FUNCTION_BLOCK

METHOD FFCombinedFunctionality
VAR_INPUT
END_VAR
VAR_INST
    fbFF    :    FB_FastFault;
    fbFFO    :    FB_HardwareFFOutput;

END_VAR
TEST('FFCombinedFunctionality');

//Clear initial faults
fbFFO.i_xReset:=TRUE; //Reset requested at start of cycle

fbFF(io_fbFFHWO := fbFFO, //FF reset somehwere in code
    i_xOK := TRUE,
    i_xReset := TRUE);

fbFFO.EvaluateOutput(); //FFO eval called at end of cycle

AssertTrue(fbFFO.q_xFastFaultOut,
    'Fast fault did not clear');


//Induce fault with FF

fbFFO(i_xReset := FALSE); //Reset released at start of cycle

fbFF(io_fbFFHWO := fbFFO, //FF faulted
    i_xOK := FALSE); //Reset is still set true from last call.

fbFFO.EvaluateOutput(); //FFO eval called at end of cycle

AssertFalse(fbFFO.q_xFastFaultOut,
    'Fast fault did not trip the beam');


//FFO remains faulted until local (FF) and FFO receieve fresh reset request, and faults
//are actually cleared

//Attempt to clear FFO fails because FF still faulted
fbFFO.i_xReset := TRUE; //Reset requested

fbFF(io_fbFFHWO := fbFFO, //FF faulted
    i_xOK := FALSE,
    i_xReset := FALSE);

fbFFO.EvaluateOutput(); //FFO eval called at end of cycle

AssertFalse(fbFFO.q_xFastFaultOut,
    'Fast fault output cleared while fault remains, very bad');

//Attempt to clear FF while fault remains fails
fbFFO.EvaluateOutput(i_xReset := FALSE);

fbFFO.i_xReset := TRUE; //Reset requested

fbFF(io_fbFFHWO := fbFFO, //FF faulted
    i_xOK := FALSE,
    i_xReset := TRUE); //This reset should fail.

fbFFO.EvaluateOutput(); //FFO eval called at end of cycle

AssertFalse(fbFFO.q_xFastFaultOut,
    'Fast fault cleared while fault remains, very bad');

TEST_FINISHED();
END_METHOD

{attribute 'no_check'}
METHOD FFOvrdAndNextFault
VAR_INPUT
END_VAR
VAR_INST
    fbFF    :    FB_FastFault :=(
        i_xAutoReset := TRUE);
    fbFFO    :    FB_HardwareFFOutput := (i_sNetID:='',
        bAutoReset := TRUE);
    rFF : REFERENCE TO ST_FF;

    FirstPass : BOOL := TRUE;
    fbTimePass : BOOL := TRUE;
    OvrdActvTstDone : BOOL := FALSE;

    TestsDone : BOOL := FALSE;
    Now: DATE_AND_TIME;
    Expiration : DINT;
    OvdTime : TIME ;
    Expire: TIMESTRUCT;
//seconds:ULINT;
    specificLocalTimeToFileTime : FB_TzSpecificLocalTimeToFileTime;
    fileTime: T_FILETIME;
END_VAR
TEST('FFVetoAndNextFault');
fbTime();

// I noticed something weird going on with the PEWatcher on the L line. It was faulted, and the fast fault
// was not tripping off beam. It had been previously overridden so I was wondering why it wasn't causing a trip.

IF  FirstPass  THEN

    fbFF(io_fbFFHWO := fbFFO, //FF reset somehwere in code
        i_xOK := TRUE);

    fbFFO.ExecuteNoLog(); //FFO eval called at end of cycle

    AssertTrue(fbFFO.q_xFastFaultOut,
        'Fast fault did not clear');


    //Induce fault with FF

    fbFF(io_fbFFHWO := fbFFO, //FF faulted
        i_xOK := FALSE);

    fbFFO.ExecuteNoLog(); //FFO eval called at end of cycle

    AssertFalse(fbFFO.q_xFastFaultOut,
        'Fast fault did not trip the beam');

    rFF REF= fbFFO.astFF[fbFF.RegistrationIdx];

FirstPass := FALSE;

END_IF


IF fbTime.bValid AND fbTimePass  THEN

    Now := SystemTime_TO_DT(fbTime.systemTime);
    Expire:= fbTime.systemTime;
    Expire.wSecond:= Expire.wSecond+5;
    specificLocalTimeToFileTime(in := Tc2_Utilities.SYSTEMTIME_TO_FILETIME(Expire), tzInfo := , out => fileTime);
    Expiration := TO_DINT((SHL(DWORD_TO_ULINT(fileTime.dwHighDateTime), 32) + DWORD_TO_ULINT(fileTime.dwLowDateTime)) / 10000000 - 11644473600);;

    WRITE_PROTECTED_DINT(ADR(rFF.Ovrd.Expiration), Expiration );
    WRITE_PROTECTED_TIME(ADR(rFF.Ovrd.Timer.PT), OvrdTime );
    WRITE_PROTECTED_BOOL(ADR(rFF.Ovrd.Activate), TRUE);

    fbTimePass:=FALSE;

END_IF

// On every cycle:
fbFF(io_fbFFHWO := fbFFO, //FF faulted
    i_xOK := FALSE);

fbFFO.ExecuteNoLog(); //FFO eval called at end of cycle


IF rFF.Ovrd.Active AND rFF.BeamPermitted THEN

    AssertTrue(fbFFO.q_xFastFaultOut,
        'Fast fault should be overridden so FFO should be true');

    OvrdActvTstDone := TRUE;
END_IF

IF OvrdActvTstDone and NOT rFF.Ovrd.Active and not rff.BeamPermitted THEN

    AssertFalse(fbFFO.q_xFastFaultOut,
        'Fast fault override expired so beam should be off.');
   // AssertFalse(rFF.BeamPermitted, 'Beam should not be permitted now');

    TestsDone := TRUE;
END_IF


IF TestsDone THEN
    TEST_FINISHED_NAMED('FFVetoAndNextFault');
END_IF
END_METHOD

{attribute 'no_check'}
METHOD FFRegistration
VAR_INPUT
END_VAR
VAR_INST
    fbFF    :    FB_FastFault;
    fbFFO    :    FB_HardwareFFOutput;
    astFF : ARRAY[0..10] OF ST_FF;
END_VAR
TEST('FFRegistration');

fbFF(io_fbFFHWO := fbFFO);

AssertEquals_STRING(fbFF.sPath, fbFFO.astFF[fbFF.RegistrationIdx].Info.sPath,
    'FF registration with FFO did not succeed');

TEST_FINISHED();
END_METHOD
Related:

FB_HardwareFFOutput

{attribute 'reflection'}
{attribute 'no_check'}
FUNCTION_BLOCK FB_HardwareFFOutput
VAR CONSTANT
    FF_ARRAY_UPPER_BOUND : UINT := PMPS_PARAM.MAX_FAST_FAULTS;
END_VAR
VAR_INPUT
    {attribute 'pytmc' := '
        pv: ClearFault
        io: o
        field: DESC Might be overidden by PLC writes
     '}
    i_xReset    :    BOOL;
    {attribute 'pytmc' := '
        pv: EnableVeto
        io: o
     '}
    i_xVeto    :    BOOL;
    bAutoReset : BOOL := FALSE; // Set true for the FFO to automatically permit beam again after all fast faults are cleared
    i_sNetID : T_AmsNetID:=''; //Set to the Arbiter AmsNetID to be used for the synchronisation. An empty string means the system will sue local time
END_VAR
VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: FaultHWO
        io: i
        field: DESC Hardware Output Status
     '}
    q_xFastFaultOut    AT %Q*    :    BOOL;
    q_xValidSyncTime : BOOL;// system time bValid output True when sync is successful
END_VAR
VAR_IN_OUT

END_VAR
VAR
    {attribute 'pytmc' := '
        pv: FF
     '}
    astFF : ARRAY[1..FF_ARRAY_UPPER_BOUND] OF ST_FF;

    {attribute 'pytmc' := '
        pv: RegistrationFailure
        io: io
     '}
    xFastFaultRegFail  :   BOOL := FALSE; // Set true if a fast fault fails to register. Holds beam off.
    tFFRegFail : F_TRIG;

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

    {attribute 'pytmc' := '
        pv: OK
        io: i
     '}
    xOK    :    BOOL:= TRUE; // Current internal state of FFO, indicates if FFO will accept a reset

    rtReset    :    R_TRIG;
    rtResetandOK : R_TRIG;

    nIndex    :    UINT := 1;

    IdxOK: BOOL;

    fbTime : FB_LocalSystemTime := ( bEnable := TRUE, dwCycle := 1 ); //Get current system time, used for override
    fbTime_to_UTC: FB_TzSpecificLocalTimeToSystemTime;
    fbGetTimeZone: FB_GetTimeZoneInformation;
    fbJson : FB_JsonSaxWriter;
    pmpsTypeCode : UDINT := 0; // shows up in json as pmps_typecode
    fbLogger : FB_LogMessage := (
        eSevr := TcEventSeverity.Critical,
        eSubsystem := E_Subsystem.MPS,
        nMinTimeViolationAcceptable := PMPS_PARAM.MAX_FAST_FAULTS);

END_VAR
// <TODO> latch off beam if DI card status goes bad

END_FUNCTION_BLOCK

ACTION EvaluateOutput:
////////////////////////////////////////////////////////////////
// Critical code. Do not touch unless you know what you're doing
////////////////////////////////////////////////////////////////

//<TODO> add diagnostic for success or failure of reset
rtReset(CLK:=i_xReset);
rtResetandOK(CLK:=(rtReset.Q OR bAutoReset) AND xOK);

q_xFastFaultOut S= rtResetandOK.Q;
q_xFastFaultOut R= (NOT xOK OR xFastFaultRegFail);

//Reset OK for next cycle
xOK := True;

////////////////////////////////////////////////////////////////
END_ACTION

ACTION Execute:
EvaluateOverrides();
EvaluateOutput();
ExecuteLogging();
END_ACTION

ACTION ExecuteNoLog:
EvaluateOverrides();
EvaluateOutput();
//ExecuteLogging();
END_ACTION

{attribute 'no_check'}
METHOD EvaluateOverrides : BOOL
VAR_INPUT
END_VAR
VAR
    FF : REFERENCE TO ST_FF;
    EvalIdx: DINT := 1;
END_VAR
VAR CONSTANT
    MaxTime : DINT:= 4294080;(*49.7 days*)
END_VAR
//Get local System Time
fbTime(sNetID:=i_sNetID);
//Get Time Zone
fbGetTimeZone(sNetID:=i_sNetID,bExecute:=TRUE,tTimeout:=T#10S);
//change local time to UTC to be compatible with unix time epoch widget
fbTime_to_UTC(in:= fbTime.systemTime , tzInfo:=fbGetTimeZone.tzInfo);
q_xValidSyncTime := fbTime.bValid AND NOT fbGetTimeZone.bError;

FOR EvalIdx := 1 TO FF_ARRAY_UPPER_BOUND DO

    FF REF= astFF[EvalIdx];

    IF NOT FF.Info.InUse THEN
        CONTINUE;
    ELSE
        // Veto timer
        IF FF.Ovrd.Deactivate THEN
            FF.Ovrd.Timer.PT := T#0s; // Only needs to be set zero for one cycle
        ELSIF FF.Ovrd.Activate THEN
            FF.Ovrd.StartDT:= TO_DINT(TO_DT(SystemTime_TO_DT(fbTime_to_UTC.out)));
            IF (FF.Ovrd.StartDT < FF.Ovrd.Expiration) THEN
                FF.Ovrd.Expiration:= LIMIT(0,FF.Ovrd.Expiration, FF.Ovrd.StartDT+MaxTime );
                   FF.Ovrd.Timer.PT := TO_DT(FF.Ovrd.Expiration)- SystemTime_TO_DT(fbTime_to_UTC.out);
            END_IF
        END_IF

        // UDINT conversions for ESS module compatibility.
        FF.Ovrd.Timer(
            IN := FF.Ovrd.Activate, // Rising edge activated
            Q => FF.Ovrd.Active);

        FF.Ovrd.tOvrdActivate(CLK:=FF.Ovrd.Active);
        FF.Ovrd.OvrdActLogAck S= FF.Ovrd.tOvrdActivate.Q;

        FF.Ovrd.tOvrdExpiring(CLK:=FF.Ovrd.Active);
        FF.Ovrd.OvrdExpLogAck S= FF.Ovrd.tOvrdExpiring.Q;

        FF.Ovrd.ElapsedTime := TIME_TO_UDINT(FF.Ovrd.Timer.ET);

        FF.Ovrd.RemainingTime := MAX(0, TO_DINT(FF.Ovrd.Timer.PT) - FF.Ovrd.ElapsedTime);

        // Clear pushbuttons
        FF.Ovrd.Activate := FALSE;
        FF.Ovrd.Deactivate := FALSE;

    END_IF
END_FOR
END_METHOD

{attribute 'obsolete' := 'Use EvaluateOverrides instead.'}
METHOD EvaluateVetos : BOOL
VAR_INPUT
END_VAR

END_METHOD

{attribute 'no_check'}
METHOD ExecuteLogging : BOOL
VAR_INPUT
END_VAR
VAR
    FF : REFERENCE TO ST_FF;
    logIdx : DINT := 1;
END_VAR
VAR_INST
    HelloTimer : TOF := ( PT:=T#24h );
END_VAR
FOR logIdx := 1 to FF_ARRAY_UPPER_BOUND do

    FF REF= astFF[logIdx];

    IF NOT FF.Info.InUse THEN
        CONTINUE;
    ELSE
        IF FF.FaultAck THEN

            pmpsTypeCode := 1;
            FormulateLogJson(FF);
            fbLogger(sMsg := 'Fault, beam off', eSevr:=TcEventSeverity.Critical);

            FF.FaultAck := FALSE;
        END_IF

        IF FF.Ovrd.OvrdActLogAck THEN

            pmpsTypeCode := 2;
            FormulateLogJson(FF);

            fbLogger(sMsg := 'Override activated', eSevr:=TcEventSeverity.Warning);

            FF.Ovrd.OvrdActLogAck := FALSE;
        END_IF

        IF FF.Ovrd.OvrdExpLogAck THEN

            pmpsTypeCode := 3;
            FormulateLogJson(FF);

            fbLogger(sMsg := 'Override released', eSevr:=TcEventSeverity.Warning);

            FF.Ovrd.OvrdExpLogAck := FALSE;
        END_IF

        IF FF.ClearAck THEN

            pmpsTypeCode := 8;
            FormulateLogJson(FF);

            fbLogger(sMsg := 'Fault cleared', eSevr:=TcEventSeverity.Info);

            FF.ClearAck := FALSE;
        END_IF


    END_IF
END_FOR

// Log registration fault cleared
tFFRegFail(CLK:=xFastFaultRegFail);
IF tFFRegFail.Q THEN
    fbJson.StartObject();
    fbJson.AddKey('pmps_typecode');
    fbJson.AddUdint(5);
    fbJson.AddKey('pmps_path');
    fbJson.AddString(sPath);
    fbJson.EndObject();
    fbJson.CopyDocument(pDoc:=fbLogger.sJson, SIZEOF(fbLogger.sJson));
    fbLogger(sMsg := 'Fast fault registration failure cleared. This may be bad as a fast fault could go unoticed.',
        eSevr:=TcEventSeverity.Warning);
END_IF

// Say hello
HelloTimer.IN := NOT HelloTimer.Q;
HelloTimer();
IF HelloTimer.IN THEN
    pmpsTypeCode := 10;
    FormulateLogJson(FF);
    fbLogger(eSubsystem:=E_Subsystem.NILVALUE,
        eSevr := TcEventSeverity.Info,
        sMsg := 'FFO is alive');
    fbLogger.eSubsystem := E_Subsystem.MPS;
END_IF
END_METHOD

METHOD INTERNAL FormulateLogJson : STRING
VAR_INPUT
    FF : ST_FF;
END_VAR
fbJson.StartObject();
fbJson.AddKey('pmps_typecode');
fbJson.AddUdint(pmpsTypeCode);
fbJson.AddKey('pmps_path');
fbJson.AddString(FF.Info.sPath);
fbJson.AddKey('pmps_device_name');
fbJson.AddString(FF.Info.DevName);
fbJson.EndObject();
fbLogger.sJson := fbJson.GetDocument();
fbJson.ResetDocument();
END_METHOD

{attribute 'no_check'}
METHOD INTERNAL IdxCheckIn : BOOL
VAR_INPUT
    Idx : DINT;
    OK : BOOL;
    Reset : BOOL;
END_VAR
VAR
    stFF : ST_FF;
    BeamPermitted : BOOL;
END_VAR
Idx := LIMIT(1,Idx,FF_ARRAY_UPPER_BOUND);
stFF := THIS^.astFF[Idx];

// Updating internal fault state
stFF.OK := OK;

// Reset and latching logic
stFF.rtReset(CLK:=stFF.Reset (*epics*) OR Reset (*from other PLC logic *) );
stFF.Reset R= stFF.rtReset.Q;
stFF.bsFF(SET := stFF.rtReset.Q OR stFF.Info.AutoReset, RESET1:= NOT OK);

BeamPermitted := stFF.bsFF.Q1 OR stFF.Ovrd.Active OR (i_xVeto AND stFF.Info.Vetoable);

// Fault generation
IF NOT BeamPermitted THEN
    THIS^.xOK := FALSE; //Very clever, thanks Zach!
END_IF

// Fast fault accumulation
stFF.ftCountFault(CLK:=stFF.bsFF.Q1);
IF stFF.ftCountFault.Q THEN PMPS_GVL.AccumulatedFF := PMPS_GVL.AccumulatedFF + 1; END_IF

//Clear pushbuttons
stFF.Reset := FALSE;

// Log when this fault faults
stFF.FaultAck S= stFF.BeamPermitted AND NOT BeamPermitted;

// Log when this fault has cleared
stFF.ClearAck S= BeamPermitted AND NOT stFF.BeamPermitted;

stFF.BeamPermitted := BeamPermitted;

// Copy state back to ff struct
THIS^.astFF[Idx] := stFF;
END_METHOD

{attribute 'no_check'}
METHOD INTERNAL Register : BOOL
VAR_INPUT
    stFFInfo : ST_FFInfo := (
        sPath := '',
        Desc := '',
        TypeCode := 16#00,
        DevName := ''); // Fast fault information

END_VAR
VAR_OUTPUT
    FFOName : T_MaxString;
    Idx : UINT;
END_VAR
VAR
END_VAR
FFOName := THIS^.sPath;
Idx := THIS^.nIndex;

IF THIS^.nIndex <= FF_ARRAY_UPPER_BOUND THEN

    THIS^.astFF[MIN(THIS^.nIndex,FF_ARRAY_UPPER_BOUND)].Info:=stFFInfo;

    THIS^.nIndex := THIS^.nIndex + 1;

    Register := TRUE; // Add successful
ELSE
    fbJson.StartObject();
    fbJson.AddKey('pmps_typecode');
    fbJson.AddUdint(4);
    fbJson.AddKey('pmps_path');
    fbJson.AddString(stFFInfo.sPath);
    fbJson.AddKey('pmps_device_name');
    fbJson.AddString(stFFInfo.DevName);
    fbJson.EndObject();
    fbLogger.sJson := fbJson.GetDocument();
    fbJson.ResetDocument();
    fbLogger(sMsg:='Fast fault registration failed. Too many fast faults on this FFO.',
        eSevr:=TcEventSeverity.Warning,
        eSubsystem:=E_Subsystem.MPS);
    xFastFaultRegFail := TRUE;
    Register := FALSE; // Failed to add name to list
END_IF
END_METHOD
Related:

FB_Hgvpu

FUNCTION_BLOCK FB_Hgvpu IMPLEMENTS I_UndulatorComplex
VAR_INPUT
    fbElectronEnergy : REFERENCE TO FB_LREALFromEPICS;
END_VAR

VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: CurrentPhotonEnergy
        io: i
        field: DESC Calculated current photon energy
        field: PREC 3
        field: EGU eV
    '}
    fCurrentPhotonEnergy : LREAL;
    {attribute 'pytmc' := '
        pv: TargetPhotonEnergy
        io: i
        field: DESC Calculated desired photon energy
        field: PREC 3
        field: EGU eV
    '}
    fTargetPhotonEnergy : LREAL;
    {attribute 'pytmc' := '
        pv: SeedUndulatorNumber
        io: i
        field: DESC Seed undulator number
    '}
    nSeedUndulator : UDINT; // Set to zero when no undulators are active
    {attribute 'pytmc' := '
        pv: TargetSeedUndulatorNumber
        io: i
        field: DESC Seed undulator number for target K
    '}
    nTargetSeedUndulator : UDINT; // Set to zero when no undulators are active
END_VAR


VAR
    // From lcls-srv01: grep -e KDes  /u1/lcls/epics/ioc/data/ioc-undh-uc*/iocInfo/IOC.pvlist |sort

    {attribute 'pytmc' := 'pv: 24; link: 2450:'}
    fbSegment_24 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 25; link: 2550:'}
    fbSegment_25 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 26; link: 2650:'}
    fbSegment_26 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 27; link: 2750:'}
    fbSegment_27 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 29; link: 2950:'}
    fbSegment_29 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 30; link: 3050:'}
    fbSegment_30 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 31; link: 3150:'}
    fbSegment_31 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 32; link: 3250:'}
    fbSegment_32 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 33; link: 3350:'}
    fbSegment_33 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 34; link: 3450:'}
    fbSegment_34 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 35; link: 3550:'}
    fbSegment_35 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 36; link: 3650:'}
    fbSegment_36 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 37; link: 3750:'}
    fbSegment_37 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 38; link: 3850:'}
    fbSegment_38 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 39; link: 3950:'}
    fbSegment_39 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 40; link: 4050:'}
    fbSegment_40 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 41; link: 4150:'}
    fbSegment_41 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 42; link: 4250:'}
    fbSegment_42 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 43; link: 4350:'}
    fbSegment_43 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 44; link: 4450:'}
    fbSegment_44 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 45; link: 4550:'}
    fbSegment_45 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 46; link: 4650:'}
    fbSegment_46 : FB_UndulatorSegment;

    fbSegment : ARRAY [iLowBound_LUnd..iHighBound_LUnd] OF POINTER TO FB_UndulatorSegment;
    fbCurrentSegment : REFERENCE TO FB_UndulatorSegment;

    iIndex : UDINT;

    bInitialized : BOOL := FALSE;

END_VAR

VAR CONSTANT
    {attribute 'pytmc' := '
        pv: FirstSegment
        io: i
    '}
    iLowBound_LUnd  : UDINT := 24;

    {attribute 'pytmc' := '
        pv: LastSegment
        io: i
    '}
    iHighBound_LUnd : UDINT := 46;

    {attribute 'pytmc' := '
        pv: Period
        io: i
        field: EGU mm
    '}
    fPeriod_mm_LUnd : LREAL := 26.0;

    {attribute 'pytmc' := '
        pv: LowK
        io: i
    '}
    fLowK_LUnd : LREAL := 0.54;

    {attribute 'pytmc' := '
        pv: HiK
        io: i
    '}
    fHiK_LUnd : LREAL := 2.6;
END_VAR
IF NOT bInitialized THEN
    Init();
END_IF

UndAdrUpdate();

nSeedUndulator := 0;
nTargetSeedUndulator := 0;

FOR iIndex := iLowBound_LUnd TO iHighBound_LUnd DO
    IF fbSegment[iIndex] <> 0 THEN
        fbCurrentSegment REF= fbSegment[iIndex]^;
        fbCurrentSegment(fbElectronEnergy:=fbElectronEnergy);

        //Mark the seed undulator, first undulator operating within K bounds
        IF fbCurrentSegment.xActive AND nSeedUndulator = 0 THEN
            nSeedUndulator := iIndex;
            fCurrentPhotonEnergy := fbCurrentSegment.fPhotonEnergyAct;
        END_IF

        IF fbCurrentSegment.xTargetActive AND nTargetSeedUndulator = 0 THEN
            nTargetSeedUndulator := iIndex;
            fTargetPhotonEnergy := fbCurrentSegment.fPhotonEnergyDes;
        END_IF
    END_IF
END_FOR

IF nSeedUndulator = 0 THEN
    fCurrentPhotonEnergy := 0;
END_IF

IF nTargetSeedUndulator = 0 THEN
    fTargetPhotonEnergy := 0;
END_IF

END_FUNCTION_BLOCK

ACTION Init:
UndAdrUpdate();


    FOR iIndex := iLowBound_LUnd TO iHighBound_LUnd DO
            IF fbSegment[iIndex] <> 0 THEN
            fbCurrentSegment REF= fbSegment[iIndex]^;
            fbCurrentSegment.fPeriod_mm := fPeriod_mm_LUnd;
            fbCurrentSegment.fLowK := fLowK_LUnd;
            fbCurrentSegment.fHiK := fHiK_LUnd;
        END_IF
    END_FOR

    bInitialized := TRUE;
END_ACTION

ACTION UndAdrUpdate:
fbSegment[24] := ADR(fbSegment_24);
fbSegment[25] := ADR(fbSegment_25);
fbSegment[26] := ADR(fbSegment_26);
fbSegment[27] := ADR(fbSegment_27);
fbSegment[28] := 0;
fbSegment[29] := ADR(fbSegment_29);
fbSegment[30] := ADR(fbSegment_30);
fbSegment[31] := ADR(fbSegment_31);
fbSegment[32] := ADR(fbSegment_32);
fbSegment[33] := ADR(fbSegment_33);
fbSegment[34] := ADR(fbSegment_34);
fbSegment[35] := ADR(fbSegment_35);
fbSegment[36] := ADR(fbSegment_36);
fbSegment[37] := ADR(fbSegment_37);
fbSegment[38] := ADR(fbSegment_38);
fbSegment[39] := ADR(fbSegment_39);
fbSegment[40] := ADR(fbSegment_40);
fbSegment[41] := ADR(fbSegment_41);
fbSegment[42] := ADR(fbSegment_42);
fbSegment[43] := ADR(fbSegment_43);
fbSegment[44] := ADR(fbSegment_44);
fbSegment[45] := ADR(fbSegment_45);
fbSegment[46] := ADR(fbSegment_46);
END_ACTION

PROPERTY rCurrentPhotonEnergy : REAL
VAR
END_VAR
rCurrentPhotonEnergy := fCurrentPhotonEnergy;
END_PROPERTY

PROPERTY rTargetPhotonEnergy : REAL
VAR
END_VAR
rTargetPhotonEnergy := fTargetPhotonEnergy;
END_PROPERTY
Related:

FB_JsonDocToSafeBP

FUNCTION_BLOCK FB_JsonDocToSafeBP
VAR_INPUT
    bExecute             : BOOL; //Rising Edge
    jsonDoc                 : SJsonValue;
    sDeviceName : STRING;
END_VAR
VAR_OUTPUT
    bHasDevice              : BOOL;
    bHasAllStates           : BOOL;
    bHasAllParameters :BOOL := TRUE;
    bBusy                : BOOL;
    bError               : BOOL;
    nErrId               : UDINT;
    sErrMsg                          :STRING;
END_VAR
VAR_IN_OUT
    //ARRAY [1.. MOTION_GVL.MAX_STATES] ;
    arrStates: ARRAY[*] OF ST_DbStateParams;
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR
    (* JSON *)
    fbJson                  : FB_JsonDomParser;
    jsonProp                : SJsonValue;
    jsonValue               : SJsonValue;
    jsonParam               : SJsonValue;
    jsonnTran      : SJsonValue;
    jsonnRate      : SJsonValue;
    Step: INT;
    index: DINT;
    nStateCount:DINT;
    RisingEdge           : R_TRIG;

    (*Logger*)
    tNewMessage : R_TRIG;
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.MPS, nMinTimeViolationAcceptable:=10);
    (*FFO*)
    FFO    :    FB_FastFault :=(
        i_Desc := 'Fault occurs when there is an error loading safe beam parameters from json file',
        i_TypeCode := 16#FF13);
    sbuffReadSmall: INT;
END_VAR
(* Retrieve JSON content *)
RisingEdge(CLK:=bExecute);

CASE Step OF
    0:     (* Idle state *)
          IF RisingEdge.Q THEN
               bBusy := TRUE;
               bError:= FALSE;
               nErrId:=0;
               sErrMsg :='';
               Step := 20;
               fbLogger(sMsg:='Start Parsing Json DOC.', eSevr:=TcEventSeverity.Info);
          END_IF

     20: (*Find the Device Name*)
        bHasDevice := fbJson.HasMember(jsonDoc, sDeviceName);
        IF (bHasDevice) THEN
            jsonProp := fbJson.FindMember(jsonDoc, sDeviceName);
            IF jsonProp <> 0 THEN
                Step := Step+1;
                ELSE
                    Step := 900 + Step;
                    nErrId := Step;
                    bError := TRUE;
                    sErrMsg:=  CONCAT('Error device name not found in Json File : ',sDeviceName);
                END_IF
        ELSE
            Step := 900 + Step;
            nErrId := Step;
            bError := TRUE;
            sErrMsg:=  CONCAT('Error device name not found in Json File : ',sDeviceName);
        END_IF

     21: (*Check device States in DOM*)
             nStateCount :=0;
             FOR Index:=LOWER_BOUND(arrStates,1) TO UPPER_BOUND(arrStates,1) BY 1 DO
                  IF NOT ((arrStates[Index].sPmpsState= '') OR (arrStates[Index].sPmpsState= 'Invalid')) THEN
                      IF NOT (fbJson.HasMember(jsonProp, arrStates[Index].sPmpsState)) THEN
                              Step := 900+ Step;
                              nErrId := Step;
                              bError := TRUE;
                              sErrMsg:= CONCAT('Error loading device state in Json File : ',arrStates[Index].sPmpsState);
                              fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical);
                              EXIT;
                        ELSE
                            nStateCount :=nStateCount+1;
                      END_IF
                   ELSE
                       bHasAllStates :=TRUE;
                       EXIT;
                  END_IF
            END_FOR

           Step := Step+1;

     22: //Make sure state array input has State name data
          IF (nStateCount =0) THEN
              bHasAllStates :=FALSE;
              Step := 900+ Step;
              nErrId := Step;
              bError := TRUE;
              sErrMsg:= CONCAT('Error Input State array is empty : ',sDeviceName);
         ELSE
             Step := Step+1;
         END_IF;

     23:  (*loop Device State Arrays to find load all States BP*)
          FOR Index:=LOWER_BOUND(arrStates,1) TO UPPER_BOUND(arrStates,1) BY 1 DO
              IF NOT (arrStates[Index].sPmpsState= '') THEN
                  IF(NOT M_LoadSafeBP(arrStates[Index].sPmpsState, Index)) THEN
                      Step := 900+ Step;
                      fbLogger.sMsg := CONCAT('Error Parsing beam parameters - Device/States: ', arrStates[Index].sPmpsState);
                      fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical);
                  END_IF
               ELSE
                    EXIT;
               END_IF
          END_FOR
          IF (bHasAllParameters) THEN Step := 30;
              ELSE Step := 900+ Step;
          END_IF

     30: (*ALL good here*)
         fbLogger.sMsg := CONCAT('Complete Parsing Json Doc. Device: ', sDeviceName);
        fbLogger.sMsg := CONCAT(fbLogger.sMsg ,' - States Count: ');
        fbLogger.sMsg := CONCAT(fbLogger.sMsg ,TO_STRING(nStateCount));
          fbLogger(eSevr:=TcEventSeverity.Info);
         bExecute:= FALSE;
        bBusy:=FALSE;
        Step := 0;
        FFO.i_xOK := TRUE;
END_CASE


//Fault in error state
IF(Step>=900) THEN
    FFO.i_xOK := FALSE;
    bBusy:=FALSE;
    Step:=0;
END_IF
ACT_FFO();
ACT_LOGGER();

END_FUNCTION_BLOCK

ACTION ACT_FFO:
(*FAST FAULT*)
FFO(i_xOK := ,
    i_xReset := ,
    i_xAutoReset :=TRUE,
    i_DevName := sDeviceName,
    io_fbFFHWO := io_fbFFHWO);
END_ACTION

ACTION ACT_Logger:
// Log valve open
tNewMessage(CLK:= NOT(FbLogger.sMsg = sErrMsg) AND NOT (sErrMsg =''));
IF tNewMessage.Q THEN fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION

METHOD M_LoadSafeBP : BOOL
VAR_INPUT
    sStateName : STRING;
    Index:DINT;
END_VAR
VAR
    //bHasAllParameters : BOOL := TRUE;
    sAperture:STRING;
    nAperture:INT := -1;
    sEV:STRING;
    sBC:STRING;
    nIndex:INT;
END_VAR
bHasAllParameters :=  fbJson.HasMember(jsonProp, sStateName);
jsonValue := fbJson.FindMember(jsonProp, sStateName);
IF bHasAllParameters THEN
    //nTran
    bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'nTran');
    IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading nTran', eSevr:=TcEventSeverity.Critical); END_IF
    jsonnTran := fbJson.FindMember(jsonValue, 'nTran');
    arrStates[Index].stBeamParams.nTran := TO_REAL(fbJson.GetString(jsonnTran));

    //nRate
    bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'nRate');
    IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading nRate', eSevr:=TcEventSeverity.Critical); END_IF
    jsonnRate := fbJson.FindMember(jsonValue, 'nRate');
    arrStates[Index].stBeamParams.nRate:= TO_UDINT(fbJson.GetString(jsonnRate));

    //neVRange
    bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'neVRange');
    jsonParam := fbJson.FindMember(jsonValue, 'neVRange');
    sEV:=(fbJson.GetString(jsonParam));
    bHasAllParameters R= NOT (LEN(sEV)=32);
    This^.arrStates[Index].stBeamParams.neVRange :=0;
    IF (NOT bHasAllParameters) THEN
        fbLogger(sMsg:= 'Error Loading eVRange', eSevr:=TcEventSeverity.Critical);
        ELSE
            FOR nIndex := 0 TO PMPS_GVL.g_cBoundaries BY 1 DO
                This^.arrStates[Index].stBeamParams.neVRange := SHL(This^.arrStates[Index].stBeamParams.neVRange,1);
                This^.arrStates[Index].stBeamParams.neVRange := This^.arrStates[Index].stBeamParams.neVRange+HEXASCNIBBLE_TO_BYTE(sEV[nIndex]);
            END_FOR
    END_IF

    //nBeamClassRange
    bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'nBeamClassRange');
    jsonParam := fbJson.FindMember(jsonValue, 'nBeamClassRange');
    sBC:=(fbJson.GetString(jsonParam));
    bHasAllParameters R= NOT (LEN(sBC)=15);
    arrStates[Index].stBeamParams.nBCRange :=0;
    IF (NOT bHasAllParameters) THEN
            fbLogger(sMsg:= 'Error Loading Beam Class Range. Make sure Bitmask is equal than 15 bits.', eSevr:=TcEventSeverity.Critical);
    ELSE
        FOR nIndex := 0 TO 14 BY 1 DO
            arrStates[Index].stBeamParams.nBCRange := SHL(arrStates[Index].stBeamParams.nBCRange,1);
            arrStates[Index].stBeamParams.nBCRange := arrStates[Index].stBeamParams.nBCRange+HEXASCNIBBLE_TO_BYTE(sBC[nIndex]);
        END_FOR
     END_IF

    //ApertureName and Values
    bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'ap_name');
    jsonParam := fbJson.FindMember(jsonValue, 'ap_name');
    sAperture := fbJson.GetString(jsonParam);
{IF defined (L)}
    IF ('SL1L0' = sAperture) THEN nAperture := L_Apertures.SL1L0;
    ELSIF ('SL2L0' = sAperture) THEN nAperture := L_Apertures.SL2L0;
    END_IF
{ELSIF defined (K)} // Attribute 'to_string' only build 4024 TO_STRING(PMPS.K_Apertures.SL1K0) - need another non hardcoded way
    IF ('SL1K0' = sAperture) THEN nAperture := K_Apertures.SL1K0;
    ELSIF ('SL2K0' = sAperture) THEN nAperture := K_Apertures.SL2K0;
    END_IF
{END_IF}
    IF (nAperture>-1) THEN
        //Gap Height
        bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'ap_ygap');
        jsonParam := fbJson.FindMember(jsonValue, 'ap_ygap');
        arrStates[Index].stBeamParams.astApertures[nAperture].Height := TO_REAL(fbJson.GetString(jsonParam));

        //Gap Width
        bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'ap_xgap');
        jsonParam := fbJson.FindMember(jsonValue, 'ap_xgap');
        arrStates[Index].stBeamParams.astApertures[nAperture].Width := TO_REAL(fbJson.GetString(jsonParam));

        IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading aperture gap', eSevr:=TcEventSeverity.Critical); END_IF
    END_IF

    //Read Transition ID for the State
    bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'id');
    jsonParam := fbJson.FindMember(jsonValue, 'id');
    IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading Assertion ID', eSevr:=TcEventSeverity.Critical); END_IF
    arrStates[Index].nRequestAssertionID := TO_UDINT(fbJson.GetInt(jsonParam));

    //Set to True to indicate this state has been initialized
    arrStates[Index].bBeamParamsLoaded := bHasAllParameters;

    //Read additional Non-BP Parameters
    bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'reactive_temp');
    jsonParam := fbJson.FindMember(jsonValue, 'reactive_temp');
    IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading Temperature Threshold', eSevr:=TcEventSeverity.Critical); END_IF
    arrStates[Index].stReactiveParams.nTempSP := TO_REAL(fbJson.GetString(jsonParam));

    bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'reactive_pressure');
    jsonParam := fbJson.FindMember(jsonValue, 'reactive_pressure');
    IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading Pressure Threshold', eSevr:=TcEventSeverity.Critical); END_IF
    arrStates[Index].stReactiveParams.nPressSP := TO_REAL(fbJson.GetString(jsonParam));
END_IF

IF NOT(     bHasAllParameters) THEN
    nErrId := 900+Step;
    bError := TRUE;
    sErrMsg:= CONCAT('Error loading BP Data - Device-State: ',sStateName);
    fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical);
END_IF

M_LoadSafeBP:=bHasAllParameters;



//TODO
//ADD the rest of the BP
END_METHOD
Related:

FB_JsonFileToJsonDoc

FUNCTION_BLOCK FB_JsonFileToJsonDoc
VAR_INPUT
    bExecute             : BOOL; //Rising Edge
    sPLCName                         : STRING;
    sSrcNetId            : T_AmsNetId;
    sSrcPathName         : T_MaxString;
END_VAR
VAR_OUTPUT
    PMPS_jsonDoc            : SJsonValue;
    bHasPLC              : BOOL;
    bBusy                : BOOL;
    bError               : BOOL;
    nErrId               : UDINT;
    sErrMsg                          :STRING;
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR
    (*Get AMS Net ID*)
    fb_GetLocalAmsNetId: FB_GetLocalAmsNetId;

    (* JSON *)
    fbJson                  : FB_JsonDomParser;
    jsonDoc                 : SJsonValue;
    jsonProp                : SJsonValue;

    (*File*)
    fbFileOpen           : FB_FileOpen;
    fbFileClose          : FB_FileClose;
    fbFileRead           : FB_FileRead;
    hSrcFile             : UINT := 0;(* File handle of the source file *)
    Step: INT;
    index: DINT;
    RisingEdge           : R_TRIG;
    (* Buffer *)
    sbuffRead                       :STRING(100000);
    cbReadLength         : UDINT := 0;
    nFileLength      : UDINT := 0;
    bfbJsonExceptionRaised: BOOL;
    tTimeOut             : TIME := DEFAULT_ADS_TIMEOUT;
    bInit : BOOL;
    (*Logger*)
    tNewMessage : R_TRIG;
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.MPS, nMinTimeViolationAcceptable:=10);
    (*FFO*)
    FFO    :    FB_FastFault :=(
        i_Desc := 'Fault occurs when there is an error reading json file',
        i_TypeCode := 16#FF13);
END_VAR
(* Get Local AMS net ID*)
IF NOT (bInit) THEN
    fb_GetLocalAmsNetId.bExecute := TRUE;
    bInit :=  TRUE;
END_IF
fb_GetLocalAmsNetId(bExecute:=);

(* Retrieve JSON content *)
RisingEdge(CLK:=bExecute);
CASE Step OF
    0:     (* Idle state *)
          IF RisingEdge.Q THEN
               bBusy := TRUE;
               bError:= FALSE;
               nErrId:=0;
               Step := 1;
               cbReadLength:=0;
               hSrcFile:=0;
               fbLogger(sMsg:='Start Reading Json File.', eSevr:=TcEventSeverity.Info);
               //Clear all json values and read buffer;
               PMPS_jsonDoc := 0;
               jsonDoc:=0;
               jsonProp:=0;
               FOR index:=1  TO 100000 DO
                  sbuffRead[index]:=0;   //clear buffer
               END_FOR
               sErrMsg:='';
               nFileLength:=0;
               jsonDoc := fbJson.ParseDocument(sbuffRead);

          END_IF
    1: (* Open source file *)
           sSrcNetId := fb_GetLocalAmsNetId.AddrString;
          fbFileOpen( bExecute := FALSE );
          fbFileOpen( sNetId := sSrcNetId, sPathName := sSrcPathName,
                         nMode := FOPEN_MODEREAD OR FOPEN_MODETEXT,
                         ePath := PATH_GENERIC, tTimeout := tTimeOut, bExecute := TRUE );
          Step := Step + 1;

    2: (* Check Open file was successful*)
          fbFileOpen( bExecute := FALSE );
          IF NOT fbFileOpen.bBusy THEN
               IF fbFileOpen.bError THEN
                    nErrId := fbFileOpen.nErrId;
                    bError := TRUE;
                    sErrMsg:= CONCAT('Error Opening Json File. E:', TO_STRING(nErrId));
                    Step := 900 + Step;
               ELSE
                    hSrcFile := fbFileOpen.hFile;
                    Step := Step + 1;
               END_IF
          END_IF

    3: (* Read data from source file to buffer string*)
          cbReadLength := 0;
          fbFileRead( bExecute:= FALSE );
          fbFileRead( sNetId:=sSrcNetId, hFile:=hSrcFile,
                         pReadBuff:= ADR(sbuffRead), cbReadLen:= SIZEOF(sbuffRead),
                         bExecute:=TRUE, tTimeout:=tTimeOut );
          Step := Step + 1;

    4: (* Check read file was successful and data read in buffer string*)
         fbFileRead( bExecute:= FALSE );
          IF NOT fbFileRead.bBusy THEN
               IF fbFileRead.bError THEN
                    nErrId := fbFileRead.nErrId;
                    bError := TRUE;
                    sErrMsg:= CONCAT('Error reading Json File. E: ', TO_STRING(nErrId));
                    Step := 900 +Step;
               ELSE
                    cbReadLength := fbFileRead.cbRead;
                    nFileLength := nFileLength + cbReadLength;
                    Step := Step + 1 ;
               END_IF
          END_IF

     5: (* Check Bytes read*)
         IF( nFileLength =0) THEN // oh no, we read zero bytes
            nErrId := fbFileRead.nErrId;
            bError := TRUE;
            sErrMsg:= CONCAT( 'Zero bytes were read from the Json File. E:', TO_STRING(nErrId));
            Step := 900 +Step;
        ELSE // we read something, lets log about it
            fbLogger(sMsg:=CONCAT('Total numbers of Bytes read from file : ', TO_STRING(nFileLength)), eSevr:=TcEventSeverity.Info);
            Step := Step + 1 ;
       END_IF;




     6: (* Close source file *)
        fbFileClose( bExecute := FALSE );
        fbFileClose( sNetId:=sSrcNetId, hFile:=hSrcFile, bExecute:=TRUE, tTimeout:=tTimeOut );
          Step := Step + 1;

     7 :  (*Check file was closed successfully.*)
        fbFileClose( bExecute := FALSE );
          IF NOT fbFileClose.bBusy THEN
               IF fbFileClose.bError THEN
                    nErrId := fbFileClose.nErrId;
                    bError := TRUE;
                    Step:=900+Step;
                    sErrMsg:= CONCAT('Error closing Json File. E:', TO_STRING(nErrId));
               END_IF
               Step := Step + 1;
               hSrcFile := 0;
          END_IF

      8: (* Error or ready => Cleanup *)
          IF (hSrcFile <> 0 ) THEN
               Step := 6; (* Close the source file *)
          ELSE
              (* Ready *)
               Step := 10;
          END_IF

     10: (*Parse read buffer string to JSON DOM object*)
         //cbJsonParserLength := fbJson.CopyDocument(sbuffRead,cbReadLength);
        jsonDoc := fbJson.ParseDocument(sbuffRead);
        bfbJsonExceptionRaised:= fbJson.ExceptionRaised();
        IF (jsonDoc <> 0) AND NOT (bfbJsonExceptionRaised) THEN
            Step := 20;
        ELSE
            Step := 900 + Step;
            nErrId := Step;
            bError := TRUE;
            sErrMsg:= CONCAT('Error Parsing JSON Data. S:',TO_STRING(nErrId));
        END_IF


     20: (*Find the PLC Name*)
        bHasPLC := fbJson.HasMember(jsonDoc, sPLCName);
        IF (bHasPLC) THEN
            jsonProp := fbJson.FindMember(jsonDoc, sPLCName);
            IF jsonProp <> 0 THEN
                Step := Step+1;
                ELSE
                    Step := 900 + Step;
                    nErrId := Step;
                    bError := TRUE;
                    sErrMsg:=  CONCAT('Error Loading PLC data from Json File : ',sPLCName);
                END_IF
        ELSE
            Step := 900 + Step;
            nErrId := Step;
            bError := TRUE;
            sErrMsg:=  CONCAT('Error PLC name not found in Json File : ',sPLCName);
        END_IF

    21: (*write to the PMPS JSonDoc variable*)
      PMPS_jsonDoc := jsonProp;
      Step := 30;

    30: (*ALL good here*)
        fbLogger(sMsg:='Reading and Parsing Json File Completed Successfully.', eSevr:=TcEventSeverity.Info);
         // Release allocated memory
        //jsonDoc := fbJson.NewDocument();
        bExecute:= FALSE;
        bBusy:=FALSE;
        FFO.i_xOK := TRUE;
        Step := 0;

END_CASE

//Fault in error state
IF(Step>=900) THEN
    FFO.i_xOK := FALSE;
    bBusy:=FALSE;
    Step:=0;
END_IF
ACT_FFO();
ACT_LOGGER();

END_FUNCTION_BLOCK

ACTION ACT_FFO:
(*FAST FAULT*)
FFO(i_xOK := ,
    i_xReset := ,
    i_xAutoReset :=TRUE,
    i_DevName := sPLCName,
    io_fbFFHWO := io_fbFFHWO);
END_ACTION

ACTION ACT_Logger:
// Log valve open
tNewMessage(CLK:= NOT(FbLogger.sMsg = sErrMsg) AND NOT (sErrMsg =''));
IF tNewMessage.Q THEN fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION
Related:

FB_KPhotonEnergy

FUNCTION_BLOCK FB_KPhotonEnergy EXTENDS FB_PhotonEnergy
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    {attribute 'pytmc' := '
        pv: eEnrg
        link: BEND:DMPS:400:BACT
        field: EGU GeV
    '}
    fbSxuElectronEnergy : FB_LREALFromEPICS;

    {attribute 'pytmc' := '
        pv: EEnergy
        io: i
        field: DESC Electron Energy
        field: EGU GeV
    '}
    fElectronEnergyVal : LREAL;

    {attribute 'pytmc' := '
        pv: EEnergyValid
        io: i
        field: DESC Electron Energy Valid
    '}
    bElectronEnergyValid : BOOL;

    {attribute 'pytmc' := '
        pv: UND
        link: USEG:UNDS:
    '}
    fbSxu : FB_SXU;

END_VAR
// Update PVs
fbSxuElectronEnergy();

fElectronEnergyVal := fbSxuElectronEnergy.fValue;
bElectronEnergyValid := fbSxuElectronEnergy.bValid;

fbSxu(fbElectronEnergy:=fbSxuElectronEnergy);

SUPER^(BP:=SUPER^.BP, Undulator:=fbSxu );

END_FUNCTION_BLOCK
Related:

FB_KStopper

FUNCTION_BLOCK FB_KStopper EXTENDS FB_StopperWatcher
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
END_VAR
VAR
END_VAR
SUPER^(stCurrentBP:=SUPER^.stCurrentBP);

END_FUNCTION_BLOCK

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
    eStopper : K_Stopper;
    sStopperName : STRING;
END_VAR
Stopper := eStopper;
StopperName := sStopperName;
END_METHOD
Related:

FB_KVetoDevice

FUNCTION_BLOCK FB_KVetoDevice EXTENDS FB_VetoDevice
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
SUPER^(stCurrentBP:=SUPER^.stCurrentBP);

END_FUNCTION_BLOCK

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
    eVetoDeviceIN : K_Stopper := K_Stopper.DEFAULT;
    eVetoDeviceOUT : K_Stopper := K_Stopper.DEFAULT;
    sVetoDeviceName : STRING;
END_VAR
VetoDevice_IN := eVetoDeviceIN;
VetoDevice_OUT := eVetoDeviceOUT;
VetoDeviceName := sVetoDeviceName;
END_METHOD
Related:

FB_LinearDeviceStateTable

(* This function block implements simple database. Data element values are stored in the hash table.  *)
{attribute 'no_check'}
FUNCTION_BLOCK FB_LinearDeviceStateTable
VAR_INPUT
    key         : DWORD := 0;(* Entry key: used by A_Lookup, A_Remove method, the key variable is also used by A_Add method *)
    putPosPtr    : POINTER TO T_HashTableEntry := 0;(* Hash table entry position pointer (used by A_Find, A_GetNext, A_GetPrev) *)
    putValue     : ST_DeviceState;(* Hash table entry value (used by A_AddHead, A_AddTail, A_Find )*)
END_VAR
VAR_OUTPUT
    bOk            : BOOL := FALSE;(* TRUE = Success, FALSE = Failed *)
    getPosPtr    : POINTER TO T_HashTableEntry := 0;(* Returned hash table entry position pointer *)
    getValue     : ST_DeviceState;(* Returned hash table entry value *)
    nCount        : UDINT := 0;(* Hash table size (number of used entries, used by A_Count) *)
END_VAR
VAR
    dataPool    : ARRAY[0..PMPS_GVL.MAX_DEVICE_STATES] OF ST_DeviceState;(* Structured data element pool *)
    entries        : ARRAY[0..PMPS_GVL.MAX_DEVICE_STATES] OF T_HashTableEntry;(* Max. number of hash table entries. The value of table entry = 32 bit integer (pointer to dataPool-array-entry) *)
    fbTable     : FB_HashTableCtrl;(* basic hash table control function block *)
    hTable         : T_HHASHTABLE;(* hash table handle *)
    pRefPtr        : POINTER TO ST_DeviceState := 0;
    indexOfElem    : ULINT;(* Integer value (max. size: x86=>32bit, x64=>64bit)*)
END_VAR
;

END_FUNCTION_BLOCK

ACTION A_Add:
(* Adds entry to the table *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_Add( hTable := hTable, key := key, putValue := 16#0000_0000(* we will set this value later *), getPosPtr=>getPosPtr, bOk=>bOk );(* Add new element to the table, getPosPtr points to the new entry *)
IF fbTable.bOk THEN(* Success *)
    fbTable.A_GetIndexAtPosPtr( hTable := hTable, putPosPtr := getPosPtr, getValue =>indexOfElem, bOk=>bOk );(* Get array index of getPosPtr entry *)
    IF fbTable.bOk THEN(* Success *)
        pRefPtr     := ADR( dataPool[indexOfElem] );(* Get pointer to the data element *)

        pRefPtr^ := putValue;(* copy application value *)

        fbTable.A_Add( hTable := hTable, key := key, putValue := pRefPtr, bOk=>bOk );(* Assign the entry value = pointer to the data element *)
        IF fbTable.bOk THEN(* Success *)
            getValue := putValue;
        END_IF
    END_IF
END_IF
END_ACTION

ACTION A_Count:
(* Count number of used entries *)
nCount := hTable.nCount;
bOk := TRUE;
END_ACTION

ACTION A_GetFirst:
(* Get first entry position pointer and value *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_GetFirst( hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
    getValue := pRefPtr^;
END_IF
END_ACTION

ACTION A_GetNext:
(* Get next entry position pointer and value *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
bOk := FALSE;

IF putPosPtr = 0 THEN
    RETURN;
END_IF

fbTable.A_GetNext( hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
    getValue := pRefPtr^;
END_IF
END_ACTION

ACTION A_Lookup:
(* Lookup for entry by key  *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_Lookup( key := key, hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
    getValue := pRefPtr^;
END_IF
END_ACTION

ACTION A_Remove:
(* Search for entry and remove it *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_Remove( key := key, hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
    getValue := pRefPtr^;
END_IF
END_ACTION

ACTION A_Reset:
(* Reset/initialize linked list *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
bOk := F_CreateHashTableHnd( ADR( entries ), SIZEOF( entries ), hTable );(* Intialize table handle *)
fbTable.A_Reset( hTable := hTable, bOk=>bOk );
nCount := 0;
END_ACTION
Related:

FB_LinearGovernor

(* The governor is meant to check your work and keep you from making mistakes.

Checks:
Does your state match the device position and visa versa?
Are the beam parameters safe for the device state?
Did you properly assert the transition and final beam parameters?

*)
FUNCTION_BLOCK FB_LinearGovernor
VAR_INPUT
    i_stCurrentBeamParams    :    ST_BeamParams; //Link to global beam params
    i_xMPSOverride            :    BOOL; //True releases MPS limits after a delay
    i_xTransitionRequested    :    BOOL; //Set true to request a transition
    i_xResetMPSFault    :    BOOL; //Set true to clear MPS fault
END_VAR
VAR_OUTPUT
     //True when governor has released restrictions
    q_xTransitionPermitted    :    BOOL;
    //Set if MC_Power has an error, or override mode is requested
    q_xFault    :    BOOL;
    // Indicates override mode is active
    q_xMPSLimitsOverridden: BOOL;
END_VAR
VAR_IN_OUT
    stDevice    :    ST_Device; //The governed
    Arbiter    :    FB_Arbiter;
    FastFaultOutput    :    FB_HardwareFFOutput;
END_VAR
VAR
    mcPower    :    MC_Power;

    xActuatorPositiveEnable: BOOL;
    xActuatorNegativeEnable: BOOL;

    xMPSPositiveEnable    :    BOOL;
    xMPSNegativeEnable    :    BOOL;
    lrPosition: LREAL;

    lrLatchedTargetPosition    :    LREAL;
    lrLatchedTargetTolerance    :    LREAL;
    stLatchedTargetState    :    ST_DeviceStateExt;

    lrMPSUpperLimit: LREAL;
    lrMPSLowerLimit: LREAL;
    rtNewState: R_TRIG;
    xTransitionOK: BOOL;

    tonMPSOverrideMode    :    TON := (
        PT := T#5S
    );

    srMPSFault    :    SR;
    // Actuator out of bounds fault
    xActuatorOOB: BOOL;
    //Beam parameters out of spec
    xBeamParamsOOS: BOOL;
    //Beam parameters of currently latched state

    stLatchedBeamParams: ST_BeamParams;
    stTargetState: ST_DeviceStateExt;

    rtLatchNewState    :    R_TRIG;
    xInit: BOOL := TRUE;

    stInitDeviceState: ST_DeviceStateExt;
    // Indicates an error with initialization
    xInitFault: BOOL;
    // Indicates the state could not be found in the state table. There may be something wrong in the device state machine implementation or state table instantiation.
    xStateLookupError: BOOL;
    // A transition needs to be requested at least once before we start latching any state.
    xTransitionHasBeenRequested: BOOL := FALSE;
    ffBeamParamsOK : FB_FastFault;
    ffActuatorBoundsOK    :    FB_FastFault;
END_VAR
lrPosition := stDevice.stAxis.NcToPlc.ActPos; //Actuator position

stDevice.stAxis.ReadStatus();
//Init
(* The init strategy is:
If the device is restored and it's residing at the safe position,
no PMPS faults. *)
////////////////////////////////
    IF xInit THEN
        xInit := FALSE;
        //Use nSafeSate for initialization.
        stDevice.StateTable.A_Lookup(key := stDevice.nSafeState);
        IF stDevice.StateTable.bOk THEN
            stInitDeviceState := F_DeviceState_To_DeviceStateExt(stDevice.StateTable.getValue, TRUE);
            xInitFault := FALSE;
        ELSE
            xInitFault := TRUE;
            stInitDeviceState.xValid := FALSE;
        END_IF
        lrLatchedTargetPosition := stInitDeviceState.rPosition;
        lrLatchedTargetTolerance := stInitDeviceState.rTolerance;
        stLatchedBeamParams := stInitDeviceState.stReqBeamParam;
    END_IF

// The governor will permit transitions once it verifies the transition assertion is active
// Note the governor does not verify the target state beam params are asserted, perhaps it should <TODO>
////////////////////////////////
    IF i_xTransitionRequested THEN
        xTransitionHasBeenRequested    := TRUE;

        xTransitionOK := Arbiter.CheckRequest( stDevice.stTransitionState.nStateRef) AND F_SafeBPCompare(i_stCurrentBeamParams, stDevice.stTransitionState.stReqBeamParam);

        //Set the target state for evaluation in further logic
        stdevice.StateTable.A_Lookup(key := stDevice.nTargetState);
        IF stDevice.StateTable.bOk THEN
            stTargetState := F_DeviceState_To_DeviceStateExt(stDevice.StateTable.getValue, TRUE);
            xStateLookupError := FALSE;
        ELSE
            xStateLookupError := TRUE;
            stTargetState.xValid := FALSE;
        END_IF
    ELSE
        xTransitionOK := FALSE;
    END_IF

(* Note about TransitionRequested

If TransitionRequested goes false during a transition, an MPS fault may be induced. This occurs
because the Actuator Out of Bounds fault will go back to using the original state limits, which
the axis may have already moved beyond.

To avoid this transient fault, make sure your device state machine is implemented such that the
transition request to the governor block remains high until the target position is reached.
*)

//Determine if a transition is complete and latch the new state
(* A transition is complete when:

Axis at standstill.
Axis within tolerance.
No transition req.

Once these conditions are met, the MPS limits shall re-engage to restrict the
axis motion to the tolerance around the state position and the MPS fault monitors
shall evaluate based on the newly latched target state.
*)

rtLatchNewState(CLK := stDevice.stAxis.Status.StandStill AND
    lrPosition <= stTargetState.rPosition + stTargetState.rTolerance AND
    lrPosition >= stTargetState.rPosition - stTargetState.rTolerance AND
    NOT i_xTransitionRequested AND xTransitionHasBeenRequested);

IF rtLatchNewState.Q THEN
    lrLatchedTargetPosition := stTargetState.rPosition;
    lrLatchedTargetTolerance := stTargetState.rTolerance;
    stLatchedBeamParams := stTargetState.stReqBeamParam;
    stLatchedTargetState := stTargetState;
END_IF

//Let other blocks know the governor won't interfer with transitions
q_xTransitionPermitted := xTransitionOK;

//Adjust limit ranges based on active state and current target
IF xTransitionOK THEN
    //Extend range to include target state
    lrMPSUpperLimit := MAX(lrMPSUpperLimit, stTargetState.rPosition + stTargetState.rTolerance);
    lrMPSLowerLimit := MIN(lrMPSLowerLimit, stTargetState.rPosition - stTargetState.rTolerance);
ELSE
    //Calculate the limit positions for MPS based on current state
    lrMPSUpperLimit := lrLatchedTargetPosition + lrLatchedTargetTolerance;
    lrMPSLowerLimit := lrLatchedTargetPosition - lrLatchedTargetTolerance;
END_IF


//MPS override
(* In override mode, the device will be free to move anywhere within it's travel range *)
//Permit motion after a small delay
tonMPSOverrideMode(IN:=i_xMPSOverride OR stDevice.xOverrideMPSLimits, Q=>q_xMPSLimitsOverridden);

//Virtual limit switch evaluation
xActuatorPositiveEnable    := stDevice.i_xHiLim AND lrPosition <= stDevice.lrUpperPositionLimit;
xActuatorNegativeEnable    := stDevice.i_xLoLim AND lrPosition >= stDevice.lrLowerPositionLimit;

//Evaluate MPS limit states
xMPSPositiveEnable := lrPosition <= lrMPSUpperLimit;
xMPSNegativeEnable := lrPosition >= lrMPSLowerLimit;

//Final Limit switch evaluation
mcPower.Enable_Positive := xActuatorPositiveEnable AND (tonMPSOverrideMode.Q OR xMPSPositiveEnable);
mcPower.Enable_Negative := xActuatorNegativeEnable AND (tonMPSOverrideMode.Q OR xMPSNegativeEnable);

mcPower(
    Axis := stDevice.stAxis,
    Enable := stDevice.xEnable //link to device enable <TODO>
);



//MPS Faults
//Fault if the beam is unsafe for current state
ffBeamParamsOK(i_xOK:=F_SafeBPCompare(i_stCurrentBeamParams, stLatchedBeamParams),
                i_xReset:=i_xResetMPSFault,
                io_fbFFHWO := FastFaultOutput);

//Fault if the position is outside of the permitted range
ffActuatorBoundsOK(i_xOK := (lrPosition <= lrMPSUpperLimit AND lrPosition >= lrMPSLowerLimit),
                    i_xReset:=i_xResetMPSFault,
                io_fbFFHWO := FastFaultOutput);



//Collect all other faults
q_xFault := NOT ffBeamParamsOK.o_xFFLine OR
    NOT ffActuatorBoundsOK.o_xFFLine OR
    mcPower.Error    OR
    i_xMPSOverride    OR
    xInitFault;

//Any faults trip the fast fault line for this FB
//FastFaultOutput.CheckIn(NOT q_xFault);

END_FUNCTION_BLOCK
Related:

FB_LPhotonEnergy

FUNCTION_BLOCK FB_LPhotonEnergy EXTENDS FB_PhotonEnergy
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    {attribute 'pytmc' := '
        pv: eEnrg
        link: BEND:DMPH:400:BACT
        field: EGU GeV
    '}
    fbHgvpuElectronEnergy : FB_LREALFromEPICS;

    {attribute 'pytmc' := '
        pv: EEnergy
        io: i
        field: DESC Electron Energy
        field: EGU GeV
    '}
    fElectronEnergyVal : LREAL;

    {attribute 'pytmc' := '
        pv: EEnergyValid
        io: i
        field: DESC Electron Energy Valid
    '}
    bElectronEnergyValid : BOOL;

    {attribute 'pytmc' := '
        pv: UND
        link: USEG:UNDH:
    '}
    fbHgvpu : FB_Hgvpu;

END_VAR
// Update PVs
fbHgvpuElectronEnergy();

fElectronEnergyVal := fbHgvpuElectronEnergy.fValue;
bElectronEnergyValid := fbHgvpuElectronEnergy.bValid;

fbHgvpu(fbElectronEnergy:=fbHgvpuElectronEnergy);

SUPER^(BP:=SUPER^.BP, Undulator:=fbHgvpu );

END_FUNCTION_BLOCK
Related:

FB_LStopper

FUNCTION_BLOCK FB_LStopper EXTENDS FB_StopperWatcher
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
END_VAR
VAR
END_VAR
SUPER^(stCurrentBP:=SUPER^.stCurrentBP);

END_FUNCTION_BLOCK

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
    eStopper : L_Stopper;
    sStopperName : STRING;
END_VAR
Stopper := eStopper;
StopperName := sStopperName;
END_METHOD
Related:

FB_LVetoDevice

FUNCTION_BLOCK FB_LVetoDevice EXTENDS FB_VetoDevice
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
SUPER^(stCurrentBP:=SUPER^.stCurrentBP);

END_FUNCTION_BLOCK

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
    eVetoDeviceIN : L_Stopper := L_Stopper.DEFAULT;
    eVetoDeviceOUT : L_Stopper := L_Stopper.DEFAULT;
    sVetoDeviceName : STRING;
END_VAR
VetoDevice_IN := eVetoDeviceIN;
VetoDevice_OUT := eVetoDeviceOUT;
VetoDeviceName := sVetoDeviceName;
END_METHOD
Related:

FB_MachineModeFromEPICS

FUNCTION_BLOCK FB_MachineModeFromEPICS
(*
Readback options are:

0 – NC (normal conducting, copper linac)

1 – SC (superconducting linac)

2 – Misconfigured (something in link node not set correctly.

*)
VAR_IN_OUT
    BP : ST_BeamParams;
    fbMPS_MachineMode : FB_LREALFromEPICS;
    FFO : FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
    xError : BOOL;
END_VAR
VAR

    ffModeReadBack : FB_FastFault := (
        i_DevName := 'Arbiter',
        i_Desc := 'Issue with Machine mode readback from Accelerator. Gateway or EPICS connection. Must be fixed.',
        i_TypeCode := 16#313,
        i_xAutoReset:=True);
END_VAR
VAR CONSTANT
    cFailSafeMM : USINT := 3;
END_VAR
fbMPS_MachineMode();

IF fbMPS_MachineMode.bValid THEN
    BP.nMachineMode := LREAL_TO_USINT(fbMPS_MachineMode.fValue);
ELSE
    BP.nMachineMode := cFailSafeMM;
END_IF

ffModeReadBack(i_xOK:=(fbMPS_MachineMode.bValid) AND fbMPS_MachineMode.fValue <2 , io_fbFFHWO:=FFO);

BP.xValid R= NOT fbMPS_MachineMode.bValid;

END_FUNCTION_BLOCK
Related:

FB_MachineSimulator

(* Simulates the machine responding to requests by adding a bit of delay/ ramps
*)
FUNCTION_BLOCK FB_MachineSimulator
VAR_INPUT
    i_stAssertedParams    :    ST_BeamParams;
    i_xFault    :    BOOL;

    xEnableAtt : BOOL;
    xEnablePE : BOOL;
    xEnableRate : BOOL;
END_VAR
VAR_IN_OUT
    iq_stMachineParams    :    ST_BeamParams;
END_VAR
VAR
    nTargetTran: REAL;
    tonTranTimer    :    TON := (
        PT := T#2S
        );
    fTargetPP_mJ: REAL;
    tonPPETimer: TON := (
        PT := T#2S
    );
END_VAR
//Attenuation
IF xEnableAtt THEN
    IF i_stAssertedParams.nTran <> nTargetTran THEN
        tonTranTimer(IN:=FALSE);
        nTargetTran := i_stAssertedParams.nTran;
    ELSIF NOT tonTranTimer.Q THEN
        tonTranTimer(IN:=TRUE);
    ELSE
        iq_stMachineParams.nTran := nTargetTran;
    END_IF
END_IF

//Pulse Energy
(*
IF xEnablePE THEN
    IF i_stAssertedParams.fPP_mJ <> fTargetPP_mJ THEN
        tonPPETimer(IN:=FALSE);
        fTargetPP_mJ := i_stAssertedParams.fPP_mJ;
    ELSIF NOT tonTranTimer.Q THEN
        tonPPETimer(IN:=TRUE);
    ELSE
        iq_stMachineParams.fPP_mJ := fTargetPP_mJ;
    END_IF
END_IF
*)
//Rate
IF xEnableRate THEN
    IF i_xFault THEN
        iq_stMachineParams.nRate := 0;
    ELSE
        iq_stMachineParams.nRate := i_stAssertedParams.nRate;
    END_IF
END_IF

END_FUNCTION_BLOCK
Related:

FB_PhotonEnergy

FUNCTION_BLOCK FB_PhotonEnergy
VAR_INPUT
    Undulator : I_UndulatorComplex;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
    BP : ST_BeamParams;
END_VAR
VAR
    nCurrentPhotonEnergyBitmask : UDINT;
    nTargetPhotonEnergyBitmask : UDINT;
END_VAR
//Update BP
nCurrentPhotonEnergyBitmask := F_eVRangeCalculator(Undulator.rCurrentPhotonEnergy, nCurrentPhotonEnergyBitmask);
nTargetPhotonEnergyBitmask := F_eVRangeCalculator(Undulator.rTargetPhotonEnergy, nTargetPhotonEnergyBitmask);

BP.neVRange := nCurrentPhotonEnergyBitmask OR nTargetPhotonEnergyBitmask;

END_FUNCTION_BLOCK
Related:

FB_PhotonEnergyWatcher

(*
A. Wallace 2019-4-22
The photon energy watcher ensures the current and target photon energy is within
the arbirated bounds. Target in this case means, the calculated target photon energy
from the PVs that control the mechatronics of the undulators/ the electron energy.

If there are control PVs that match the monitor PVs, the control PVs are used to
calculate a "target" photon energy. This is supposed to cover when the undulators/
electron energy is transitioning.

The abritrated bounds come from a simple AND of all the permitted ranges. See
the arbitrate action of the arbiter FB.

Note, this protection logic does not account for beam-off when determining fast-fault
status. If a device is requesting a limited range of eV, this request must be honored,
regardless of current beam-rate.
*)
{attribute 'reflection'}
FUNCTION_BLOCK FB_PhotonEnergyWatcher
VAR_INPUT
    i_stCurrentBeamParams    :    ST_BeamParams; //Link to global beam params
    i_stMachineTargetBeamParams    :    ST_BeamParams; //Link to global machine target beam params
    i_stRequestedBeamParams    :    ST_BeamParams; //Link to arbiter output or beam param. requestor
    // Reset fault
    i_xReset: BOOL;
    sName : STRING := 'PhotonEnergyWatcher';
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR
    xPhotonEnergyWithinBounds    :    BOOL;
    fbFF    :    FB_FastFault :=(
        i_DevName := sName,
        i_Desc := 'Fault occurs when the calculated machine photon energy (K value calculated by undulator gap, and electron energy) falls outside the permitted range.',
        i_TypeCode := 7 );

   {attribute 'pytmc' := '
        pv: ResidualPhotonEnergies
        io: i
        archive: 1Hz monitor
        field: DESC Portions of beam eV not permitted
        field: EGU eV-bitmask
    '}
    evResidual : DWORD;

    fbLog : FB_LogMessage := (
        eSubSystem := E_Subsystem.MPS,
        eSevr := TcEventSeverity.Critical
        );
    bLogOneShot : BOOL;
    sDevName : T_MaxString := 'Photon Energy Watcher';

    fbGetHN : FB_GetHostName;
    bInit : BOOL := TRUE;

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

    fbStr : FB_FormatString := (
        sOut := 'Non-zero photon energy residual: %32b; Req: %32b; Act: %32b');
END_VAR
IF bInit THEN
    fbGetHN(bExecute:=TRUE);
    bInit R= NOT fbGetHN.bBusy;
END_IF

xPhotonEnergyWithinBounds := (i_stCurrentBeamParams.neVRange AND i_stRequestedBeamParams.neVRange) = i_stCurrentBeamParams.neVRange;

evResidual := (i_stCurrentBeamParams.neVRange XOR i_stRequestedBeamParams.neVRange) AND i_stCurrentBeamParams.neVRange;

IF evResidual <> 0 AND bLogOneShot THEN
    fbLog.sJson := F_PMPS_JSON(
        CONCAT(fbGetHN.sHostName, sDevName),
        sPath,
        PMPS_CODES.PEW_FAULT);

    fbStr.arg1 := F_DWORD(evResidual);
    fbStr.arg2 := F_DWORD(i_stCurrentBeamParams.neVRange);
    fbStr.arg3 := F_DWORD(i_stRequestedBeamParams.neVRange);

    fbStr();

    fbLog(sMsg:=fbStr.sOut);
    bLogOneShot := FALSE;
ELSIF evResidual = 0 THEN
    bLogOneShot := TRUE;
END_IF

fbFF(i_xOK := xPhotonEnergyWithinBounds,
    io_fbFFHWO := io_fbFFHWO,);

END_FUNCTION_BLOCK
Related:

FB_PhotonEnergyWatcher_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_PhotonEnergyWatcher_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR

END_VAR
PEWatcherBasicFunction();

END_FUNCTION_BLOCK

METHOD PEWatcherBasicFunction
VAR_INPUT
END_VAR
VAR_INST
    fbPEW    :    FB_PhotonEnergyWatcher;
    fbFFO    :    FB_HardwareFFOutput;
    q_Output    :    BOOL;

    stCurBeamParams    :    ST_BeamParams;
    stMachineTargetBeamParams    :    ST_BeamParams;
    stReqBeamParams    :    ST_BeamParams;
END_VAR
TEST('PEWatcherBasicFunction');

//Clear FFO fault to start
fbFFO(i_xReset:=TRUE);

//All eV OK
stCurBeamParams.neVRange := F_eVRangeCalculator(800, 0);

stMachineTargetBeamParams.neVRange := 2#0000_0000_1000_0000;

stReqBeamParams.neVRange := 2#1111_1111_1111_1111;

fbFFO(i_xReset:=FALSE); //Reset request released

fbPEW(i_stCurrentBeamParams := stCurBeamParams,
    i_stMachineTargetBeamParams := stMachineTargetBeamParams,
    i_stRequestedBeamParams := stReqBeamParams,
    io_fbFFHWO := fbFFO,
    i_xReset := TRUE); //Clear local FF within PEW

fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output); //This simulates a completed PLC cycle
//Evaluate output should be called once at the end of a PLC cycle.

AssertTrue(q_Output,
    'Beam within limits, FFO should be true');


//Req eV moved out of range
stCurBeamParams.neVRange := F_eVRangeCalculator(800, 0);

stMachineTargetBeamParams.neVRange := F_eVRangeCalculator(800, 0);

stReqBeamParams.neVRange := 2#0000_1111_0011_1111;

fbPEW.i_xReset := FALSE; //release local FF reset req
fbPEW(i_stCurrentBeamParams := stCurBeamParams,
    i_stMachineTargetBeamParams := stMachineTargetBeamParams,
    i_stRequestedBeamParams := stReqBeamParams,
    io_fbFFHWO := fbFFO);

fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output);

AssertFalse(q_Output,
    'Limits moved beyond current and target beam, should produce a fault.');

//Beam and target eV moved back within permitted ranges, reset clears fault
stCurBeamParams.neVRange := F_eVRangeCalculator( 300, 0);

stMachineTargetBeamParams.neVRange := F_eVRangeCalculator(300, 0);

stReqBeamParams.neVRange := 2#0000_1111_0011_1111;

fbFFO(i_xReset:=TRUE); //New reset request

fbPEW.i_xReset := TRUE; //New reset request

fbPEW(i_stCurrentBeamParams := stCurBeamParams, //Beam params are now within spec
    i_stMachineTargetBeamParams := stMachineTargetBeamParams,
    i_stRequestedBeamParams := stReqBeamParams,
    io_fbFFHWO := fbFFO);

fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output);

AssertTrue(q_Output,
    'Beam within limits after reset, FFO should be true');

//At this point, the fast fault would be cleared

//Current beam eV moved out of range
stCurBeamParams.neVRange := F_eVRangeCalculator(800, 0); //Moved out of range

stMachineTargetBeamParams.neVRange := F_eVRangeCalculator(300, 0); //Still within range

stReqBeamParams.neVRange := 2#0000_1111_0011_1111;

fbPEW(i_stCurrentBeamParams := stCurBeamParams,
    i_stMachineTargetBeamParams := stMachineTargetBeamParams,
    i_stRequestedBeamParams := stReqBeamParams,
    io_fbFFHWO := fbFFO);

fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output);

AssertFalse(q_Output,
    'Current beam moved beyond set limits, should produce a fault');

//Target beam eV moved out of range
fbFFO(i_xReset:=FALSE); //Reset released
fbFFO(i_xReset:=TRUE); //New reset request comes in at the top of a cycle, but will be overridden

stCurBeamParams.neVRange := F_eVRangeCalculator(300, 0); //Now within range

stMachineTargetBeamParams.neVRange := F_eVRangeCalculator(800, 0); //Moved out of range

stReqBeamParams.neVRange := 2#0000_1111_0011_1111;

fbPEW.i_xReset := TRUE; //Attempt to reset local FF will fail
fbPEW(i_stCurrentBeamParams := stCurBeamParams,
    i_stMachineTargetBeamParams := stMachineTargetBeamParams,
    i_stRequestedBeamParams := stReqBeamParams,
    io_fbFFHWO := fbFFO);

fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output);

AssertFalse(q_Output,
    'Target beam moved beyond set limits, should produce a fault');

TEST_FINISHED();
END_METHOD
Related:

FB_PressSensor_FFO

FUNCTION_BLOCK FB_PressSensor_FFO
VAR_INPUT
    {attribute 'pytmc' := '
        pv: Press
        io: input
        field: EGU Torr
        field: PREC 2
    '}
    rPress : REAL;
    {attribute 'pytmc' := '
        pv: FAULT_SP
        io: input
        field: EGU Torr
        field: PREC 2
    '}
    fFaultThreshold : LREAL; //Faults when the threshold is reached. Trigger value.
    {attribute 'pytmc' := '
        pv: FAULT_SP_HYS
        io: input
        field: EGU %
        field: PREC 2
    '}
    fHysteresis : LREAL :=1; // percentage determining how far below the trigger value the fault should be released
    sDevName: STRING;
    bVeto : BOOL:=FALSE; // This Fault will be will not trip the beam if the bVeto is TRUE
    bAutoReset : BOOL:=TRUE;
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
    bFAULT_OK:BOOL :=FALSE;
    FFO    :    FB_FastFault :=(
        i_Desc := 'Fault occurs when the temprature trip point is reached',
        i_TypeCode := 16#f700);
    rtRESET :       R_TRIG;
    ftFAULT :       F_TRIG;
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.MPS, nMinTimeViolationAcceptable:=10);
END_VAR
//Verify Hysteresis is between 1-100, Shouldn't be 0. shouldn't be a 100 either.
fHysteresis:=  LIMIT(1, fHysteresis, 100);

// Evaluate the threshold trip point
IF (rPress >= fFaultThreshold) THEN
    bFAULT_OK := FALSE;
ELSIF (rPress < (fFaultThreshold - fFaultThreshold*fHysteresis/100)) THEN
    bFAULT_OK := TRUE;
END_IF



ACT_Logger();

(*FAST FAULT*)
FFO(i_xOK := bFAULT_OK OR bVeto,
    i_xReset :=,
    i_xAutoReset := bAutoReset,
    i_DevName := sDevName,
    io_fbFFHWO := io_fbFFHWO);

END_FUNCTION_BLOCK

ACTION ACT_Logger:
ftFAULT(CLK:= FFO.i_xOK);
rtRESET(CLK:=FFO.i_xOK);

IF(ftFAULT.Q) THEN
    fbLogger(sMsg := 'Temp Threshold Fault, beam off', eSevr:=TcEventSeverity.Critical);
END_IF

IF(rtRESET.Q) THEN
    fbLogger(sMsg := 'Temp Threshold Fault condition clear', eSevr:=TcEventSeverity.Info);
END_IF
END_ACTION
Related:

FB_RateFromEPICS

FUNCTION_BLOCK FB_RateFromEPICS

(*
enum MPSBeamRates {
    MPSRateInvalid            =  0,
    MPSRate0Hz                =  1,
    /* Previously used 2 and 3 for single shot and burst */
    MPSRate1Hz                =  4,
    MPSRate10Hz               =  5,
    MPSRate30Hz               =  6,
    MPSRate60Hz               =  7,
    MPSRate120Hz              =  8,
    MPSRateUnknown            =  9,
    MPSRateSingleShot         = 10,
    MPSRateBurstMode          = 11,
    MPSRateBurstModeNotActive = 12,
    MPSNumberOfBeamRates      = 13,
    MPSRateBurstInvalid       = 14
}
*)

VAR_IN_OUT
    BP : ST_BeamParams;
    fbBYKIK_Rate : FB_LREALFromEPICS;
    FFO : FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
    xError : BOOL;
END_VAR
VAR


    eMPSRate : E_MPSBeamRates;

    ffRateReadBack : FB_FastFault := (
        i_DevName := 'Arbiter',
        i_Desc := 'Issue with rate readback from Accelerator. Gateway or EPICS connection. Must be fixed.',
        i_TypeCode := 16#203,
        i_xAutoReset:=True);
END_VAR
VAR CONSTANT
    cFailSafeRate : UDINT := 1000001;
END_VAR
fbBYKIK_Rate();

IF fbBYKIK_Rate.bValid THEN
    eMPSRate := LREAL_TO_UINT(fbBYKIK_Rate.fValue);
ELSE
    eMPSRate := E_MPSBeamRates.MPSRateInvalid;
END_IF

CASE eMPSRate OF
    MPSRateInvalid:
        BP.nRate := cFailSafeRate;
    MPSRate0Hz:
        BP.nRate := 0;
    MPSRate1Hz:
        BP.nRate := 1;
    MPSRate10Hz:
        BP.nRate := 10;
    MPSRate30Hz:
        BP.nRate := 30;
    MPSRate60Hz:
        BP.nRate := 60;
    MPSRate120Hz:
        BP.nRate := 120;
    MPSRateUnknown:
        BP.nRate := cFailSafeRate;

ELSE
    BP.nRate := cFailSafeRate;
END_CASE

ffRateReadback(i_xOK:=fbBYKIK_Rate.bValid, io_fbFFHWO:=FFO);

BP.xValid R= NOT fbBYKIK_Rate.bValid OR
            eMPSRate = MPSRateUnknown OR
            eMPSRate = MPSRateInvalid;

END_FUNCTION_BLOCK
Related:

FB_SafeBPCompare_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_SafeBPCompare_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR

END_VAR
BeamOffIsSafe();
BPComparisonCheck();

END_FUNCTION_BLOCK

//Verify beam off is considered safe.
METHOD BeamOffIsSafe
VAR_INPUT
END_VAR
VAR
    stBeamOn_MoreConservative    :    ST_BeamParams;
    stBeamOff_LessConservative    :    ST_BeamParams;
    stBeam0Rate    :    ST_BeamParams;
    stBeam0BC    :    ST_BeamParams;
    fb_BeamClassOutputs_BCD:FB_BeamClassOutputs_BCD;
END_VAR
stBeamOn_MoreConservative  := F_SetBeamParams(
    0, //0% transmission is more conservative than 50
    16#FFFF_FFFF,
    120, //rate
    16#0001,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

stBeamOff_LessConservative:= F_SetBeamParams(
    0.5, //50% transmission, less conservative, without 0-rate this beam parameter set would be less safe than above
    16#FFFF_FFFF,
    0, //rate, hence, more conservative
    16#000F,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY); //0 beam rate means this bps is actually safer than BeamOn

// Set Machine Mode to NC
PMPS_GVL.stCurrentBeamParameters.nMachineMode := 0;

TEST('BeamOffIsSafeNC');

AssertTrue(
    F_SafeBPCompare0Rate(stBeamOff_LessConservative, stBeamOn_MoreConservative),
    'SafeBPCompare in NC does not think beam rate = 0 is safer than anything else.');
TEST_FINISHED();


TEST('BeamClass0IsNOTSafeNC');


stBeam0BC  := F_SetBeamParams(
    1, //0% transmission is more conservative than 50
    16#FFFF_FFFF,
    1000, //rate
    16#0000,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);


AssertFalse(
    F_SafeBPCompare0Rate(stBeam0BC, stBeamOn_MoreConservative),
    'SafeBPCompare beam class = 0 is NOT safer than anything else in NC mode.');
TEST_FINISHED();




//SC tests
stBeamOn_MoreConservative  := F_SetBeamParams(
    0, //0% transmission is more conservative than 50
    16#FFFF_FFFF,
    0, //rate
    16#000F,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

stBeamOff_LessConservative:= F_SetBeamParams(
    0.5, //50% transmission, less conservative, without 0-rate this beam parameter set would be less safe than above
    16#FFFF_FFFF,
    1000,
    16#0000, //zero beam, hence, more conservative
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY); //0 beam rate means this bps is actually safer than BeamOn

// Set Machine Mode to SC
PMPS_GVL.stCurrentBeamParameters.nMachineMode := 1;

TEST('BeamOffIsSafeSC');

AssertTrue(
    F_SafeBPCompare0Rate(stBeamOff_LessConservative, stBeamOn_MoreConservative),
    'SafeBPCompare in SC mode does not think beam class = 0 is safer than anything else.');
TEST_FINISHED();

TEST('Beam0RateIsNOTSafeSC');


stBeam0Rate  := F_SetBeamParams(
    1, //0% transmission is more conservative than 50
    16#FFFF_FFFF,
    0, //rate
    16#00FF,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);


AssertFalse(
    F_SafeBPCompare0Rate(stBeam0Rate, stBeamOn_MoreConservative),
    'SafeBPCompare beam rate = 0 is NOT safer than anything else in SC mode.');
TEST_FINISHED();


// Set Machine Mode to Misconfigured
PMPS_GVL.stCurrentBeamParameters.nMachineMode := 2;
TEST('WrongMachineMode');

AssertFalse(
    F_SafeBPCompare0Rate(stBeam0Rate, stBeam0BC),
    'SafeBPCompare in Misconfigured mode does not think beam rate = 0 is safer than anything else.');

AssertFalse(
    F_SafeBPCompare0Rate(stBeam0BC, stBeam0Rate),
    'SafeBPCompare in Misconfiigured mode does not think beam class = 0 is safer than anything else.');
TEST_FINISHED();
END_METHOD

//Verify BP comparison remains logical.
{attribute 'no_check'}
METHOD BPComparisonCheck
VAR_INPUT
END_VAR
VAR
    stSafer    :    ST_BeamParams;
    stNotSoSafe    :    ST_BeamParams;
    idx : UINT;

    xResult : BOOL;
    xFailedTest : BOOL; // To exit loops early
END_VAR
// Test in NC mode
PMPS_GVL.stCurrentBeamParameters.nMachineMode :=0;

TEST('BPComparisonTran');
//Attenuation
stSafer := F_SetBeamParams(
    0.51, //
    16#FFFFFFFF,
    1,
    16#0000,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

stNotSoSafe := F_SetBeamParams(
    0.61, //
    16#FFFFFFFF,
    1,
    16#0000,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

AssertTrue(
    F_SafeBPCompare(stSafer, stNotSoSafe),
    'Attenuation eval is broken (True)');

AssertFalse(
    F_SafeBPCompare(stNotSoSafe, stSafer),
    'Attenuation eval is broken (False)');

// Check at margin threshold
stSafer.nTran := stNotSoSafe.nTran*(PMPS_GVL.TRANS_SCALING_FACTOR + PMPS_PARAM.TRANS_MARGIN)/PMPS_GVL.TRANS_SCALING_FACTOR;
AssertTrue(
    F_SafeBPCompare(stSafer, stNotSoSafe),
    'Attenuation eval is broken: should be safe up to margin.');

stSafer.nTran := 1 + stNotSoSafe.nTran*(PMPS_GVL.TRANS_SCALING_FACTOR + PMPS_PARAM.TRANS_MARGIN)/PMPS_GVL.TRANS_SCALING_FACTOR;

AssertFalse(
    F_SafeBPCompare(stSafer, stNotSoSafe),
    'Attenuation eval is broken (False): should not be safe at all past margin');

TEST_FINISHED();



//Attenuator array
TEST('BPComparisonTranArray');

stSafer := F_SetBeamParams(
    0, //
    16#FFFFFFFF,
    1,
    16#0000,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

stNotSoSafe := F_SetBeamParams(
    0, //
    16#FFFFFFFF,
    1,
    16#000F,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

FOR idx:=1 TO PMPS_GVL.AUX_ATTENUATORS DO
    stSafer.astAttenuators[idx].nTran := 0.00500;
    stNotSoSafe.astAttenuators[idx].nTran := 0.00600;

    xResult := F_SafeBPCompare(stSafer, stNotSoSafe);

    AssertTrue(
    xResult,
    'Attenuation array eval is broken (True)');

    xFailedTest S= NOT xResult;

    xResult := F_SafeBPCompare(stNotSoSafe, stSafer);
    AssertFalse(
    xResult,
    'Attenuation array eval is broken (False)');

    xFailedTest S= xResult;

    // Check at margin threshold
        stSafer.astAttenuators[idx].nTran := stNotSoSafe.astAttenuators[idx].nTran*(PMPS_GVL.TRANS_SCALING_FACTOR + PMPS_PARAM.TRANS_MARGIN)/PMPS_GVL.TRANS_SCALING_FACTOR;

        xResult := F_SafeBPCompare(stSafer, stNotSoSafe);

        AssertTrue(
            xResult,
            'Attenuation eval is broken: should be safe up to margin.');
        xFailedTest S= NOT xResult;

        stSafer.astAttenuators[idx].nTran := 1 + stNotSoSafe.astAttenuators[idx].nTran*(PMPS_GVL.TRANS_SCALING_FACTOR + PMPS_PARAM.TRANS_MARGIN)/PMPS_GVL.TRANS_SCALING_FACTOR;

        xResult := F_SafeBPCompare(stSafer, stNotSoSafe);
        AssertFalse(
            xResult,
            'Attenuation eval is broken (False): should not be safe at all past margin');
        xFailedTest S= xResult;
    //////////////////////
    IF xFailedTest THEN
        stSafer.astAttenuators[idx].nTran := 0;
        stNotSoSafe.astAttenuators[idx].nTran := 0;
        xFailedTest := FALSE;
        EXIT; // Exit this loop on the first failure.
    END_IF

    stSafer.astAttenuators[idx].nTran := 0;
    stNotSoSafe.astAttenuators[idx].nTran := 0;

END_FOR

TEST_FINISHED();

stSafer.astAttenuators[idx].nTran := 0;
stNotSoSafe.astAttenuators[idx].nTran := 0;

//Aperture array
TEST('BPComparisonApertureArray');

FOR idx:=1 TO PMPS_GVL.MAX_APERTURES DO
    // Height
    stSafer.astApertures[idx].Height := 30; // narrower is safer
    stNotSoSafe.astApertures[idx].Height := 50;

    AssertTrue(
    F_SafeBPCompare(stSafer, stNotSoSafe),
    'Aperture array eval is broken on height (True)');

    AssertFalse(
    F_SafeBPCompare(stNotSoSafe, stSafer),
    'Aperture array eval is broken on height (False)');

    IF NOT F_SafeBPCompare(stSafer, stNotSoSafe) or F_SafeBPCompare(stNotSoSafe, stSafer) THEN
        EXIT; // Exit this loop on the first failure.
    END_IF

    stSafer.astApertures[idx].Height := 0;
    stNotSoSafe.astApertures[idx].Height := 0;

    // Width
    stSafer.astApertures[idx].Width := 30; // narrower is safer
    stNotSoSafe.astApertures[idx].Width := 50;

    AssertTrue(
    F_SafeBPCompare(stSafer, stNotSoSafe),
    'Aperture array eval is broken on width (True)');

    AssertFalse(
    F_SafeBPCompare(stNotSoSafe, stSafer),
    'Aperture array eval is broken on width (False)');

    IF NOT F_SafeBPCompare(stSafer, stNotSoSafe) or F_SafeBPCompare(stNotSoSafe, stSafer) THEN
        EXIT; // Exit this loop on the first failure.
    END_IF

    stSafer.astApertures[idx].Width := 0;
    stNotSoSafe.astApertures[idx].Width := 0;


END_FOR

TEST_FINISHED();

//Rate
TEST('BPComparisonRate');
stSafer := F_SetBeamParams(
    0,
    16#FFFF_FFFF,
    1,
    16#0000,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY); //

stNotSoSafe := F_SetBeamParams(
    0,
    16#FFFF_FFFF,
    10,
    16#0001,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY); //

AssertTrue(
    F_SafeBPCompare(stSafer, stNotSoSafe),
    'Rate eval is broken (True)');

AssertFalse(
    F_SafeBPCompare(stNotSoSafe, stSafer),
    'Rate eval is broken (False)');
TEST_FINISHED();

//Pulse energy
(*
TEST('BPComparisonPulseEnergy');
stSafer := F_SetBeamParams(
    0,
    1, //
    16#FFFF,
    1,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

stNotSoSafe := F_SetBeamParams(
    0,
    10, //
    16#FFFF,
    1,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
    *)
AssertTrue(
    F_SafeBPCompare(stSafer, stNotSoSafe),
    'Pulse energy eval is broken (True)');

AssertFalse(
    F_SafeBPCompare(stNotSoSafe, stSafer),
    'Pulse energy eval is broken (False)');
TEST_FINISHED();

//Photon energy
//More extensive tests elsewhere
TEST('BPComparisonPhotonEnergy');
stSafer := F_SetBeamParams(
    0,
    2#0000_0000_0000_0000_0000_0000_0000_0010,
    1,
    16#0000,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

stNotSoSafe := F_SetBeamParams(
    0,
    2#0000_0000_0000_0000_0000_0000_0000_0110,
    1,
    16#0001,
    PMPS_GVL.DUMMY_AUX_ATT_ARRAY);

AssertTrue(
    F_SafeBPCompare(stSafer, stNotSoSafe),
    'Photon energy eval is broken (True)');

AssertFalse(
    F_SafeBPCompare(stNotSoSafe, stSafer),
    'Photon energy eval is broken (False)');
TEST_FINISHED();
END_METHOD
Related:

FB_SolidAttenuator

FUNCTION_BLOCK FB_SolidAttenuator
VAR_INPUT
    i_rRequestedAttenuation    :    REAL;
END_VAR
VAR_OUTPUT
    q_xFault    :    BOOL;
    q_rCurrentAttenuation    :    REAL;
END_VAR
VAR_IN_OUT
    Arbiter    :    FB_Arbiter; //Higher level arbiter from which upstream attenuation can be requested
END_VAR
VAR
    stAttenuator    :    ST_BinarySolidAttenuator;
    nSearchStart: INT;
    nArrayLength: INT;
    axNewBladeStates    :    ARRAY [0..9]    OF    BOOL;
    nTryIndex: INT;
    rCurrentAccumulatedAttenuation: REAL;
    rTryFilter: REAL;
    rTryAttenuation: REAL;
    rRequestedAttenuation: REAL;
END_VAR
//

//Assuming the blades are ordered thickest to thinnest
REPEAT
    rTryAttenuation := rCurrentAccumulatedAttenuation + stAttenuator.axBlades[nTryIndex].rThickness;
    IF rRequestedAttenuation >= rTryAttenuation THEN
        rCurrentAccumulatedAttenuation := rTryAttenuation;
        axNewBladeStates[nTryIndex] := TRUE;
    ELSE
        axNewBladeStates[nTryIndex] := FALSE;
    END_IF
UNTIL
    nTryIndex >= nArrayLength
END_REPEAT

END_FUNCTION_BLOCK
Related:

FB_StopperWatcher

// Relays stopper state and sends a message when stopper state changes.
FUNCTION_BLOCK FB_StopperWatcher
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
    stCurrentBP : ST_BeamParams;
END_VAR
VAR
    i_StopperOutLS AT %I* : BOOL;
    i_StopperInLS AT %I* : BOOL;

    q_StopperOUT_Relay AT %Q* : BOOL;
    q_StopperIN_Relay AT %Q* : BOOL;

    Stopper : UINT;
    StopperName : STRING;
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath    :    T_MaxString;

    // Logging
    fbLogMsg : FB_LogMessage := (
        eSubSystem := E_Subsystem.MPS);

    rtIn : R_TRIG;
    rtOut : R_TRIG;

    bInit: BOOL := TRUE;
END_VAR
IF bInit THEN
    fbLogMsg.sJson := F_PMPS_JSON(StopperName, sPath, 1000);
    bInit := FALSE;
END_IF


rtIn(CLK := i_StopperInLS);
rtOut(CLK := i_StopperOutLS);

IF rtIn.Q THEN
    fbLogMsg.sMsg := CONCAT(StopperName, ' moved IN');
    fbLogMsg();
ELSIF rtOut.Q THEN
    fbLogMsg.sMsg := CONCAT(StopperName, ' moved OUT');
    fbLogMsg();
END_IF

q_StopperOUT_Relay := i_StopperOutLS;
q_StopperIN_Relay := i_StopperInLS;

// Update current beam parameters
stCurrentBP.aVetoDevices[Stopper] := i_StopperInLS;

END_FUNCTION_BLOCK
Related:

FB_SubsysToArb_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_SubsysToArb_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
BasicFunction();
VetoFunction();

END_FUNCTION_BLOCK

{attribute 'no_check'}
METHOD BasicFunction
VAR_INPUT
END_VAR
VAR
    nId    :    DWORD := 1;
    stReq    :    ST_BeamParams := (nTran:=12);
END_VAR
VAR_INST
    fbArbiter    :    FB_Arbiter(1);

    fbSubSysToArb : FB_SubSysToArbiter_IO; // Subsystem interface with beamline arbiter PLC
    pBPC : POINTER TO ST_BeamParams_IO;

    ioCurrentBP : ST_BeamParams_IO;

    FFO : FB_HardwareFFOutput; // Dummy FFO not important for these tests

    nCohortScratch : UDINT;

END_VAR
VAR CONSTANT
    SysID : DWORD := 42;
END_VAR
// This simulates a synchronous cycle between the subsystem and arbiter PLC cycles. Ie. phase is locked, and cycle time is the same. This is not always the case, so these tests should be
// trusted with a grain of salt.

TEST('Add to arbiter');

IF NOT fbArbiter.CheckRequestInPool(nID) THEN
    AssertTrue(fbArbiter.AddRequest(nId, stReq,'Device'), 'Arbiter returned false from AddRequest'); // some device asking its local arbiter for some beam parameters
END_IF

TEST_FINISHED();

///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////


TEST('Initial Elevation');
AssertTrue(fbSubSysToArb.nRequestCohort = fbSubSysToArb.q_stRequestedBP.nCohortInt, 'ReqCohort and qBP Cohort do not match');
AssertFalse(fbArbiter.nActiveCohort <> 0, 'Arbiter indicates it is included in arbitration prematurely');
AssertTrue(fbSubSysToArb.nRequestCohort <> 0, 'Cohort index should move to 1 with first request');
TEST_FINISHED_NAMED('Initial Elevation');

nCohortScratch := fbSubSysToArb.nRequestCohort;

//Ethercat transfer simulation
// Transfer of requested BP to arbiter PLC
// pBPR := ADR(fbArbToSubSys.i_RequestedBP);
// pBPR^ := fbSubSysToArb.q_stRequestedBP;

ioCurrentBP := fbSubSysToArb.q_stRequestedBP;
// NOTE: Setting the returning cohort number to something less than the request cohort number
// will prevent the sub system arbiter FB from considering itself active in arbitration.
// The next set of tests verify this.
ioCurrentBP.nCohortInt := 0;

// Transfer of current BP to sub system PLC
pBPC := ADR(fbSubSysToArb.i_stCurrentBP);
pBPC^ := ioCurrentBP;

///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////

TEST('Not yet active in arbitration');
AssertTrue(fbSubSysToArb.nActiveCohort = 0, 'Active cohort should still be zero, per ioCurrentBP.nCohortInt setting above.');
AssertFalse(fbArbiter.nActiveCohort <> 0, 'Arbiter indicates it is included in arbitration prematurely, checkrequest might be broken');
AssertTrue(fbSubSysToArb.nRequestCohort = nCohortScratch, 'Cohort index should not have changed because there have been no additional requests added to the arbiter');
TEST_FINISHED_NAMED('Not yet active in arbitration');

//Ethercat transfer simulation
// Transfer of requested BP to arbiter PLC
// pBPR := ADR(fbArbToSubSys.i_RequestedBP);
// pBPR^ := fbSubSysToArb.q_stRequestedBP;

ioCurrentBP := fbSubSysToArb.q_stRequestedBP;
// NOTE: Now we simulate the FB_ArbiterToSubSys_IO updating the cohort number to the same value as the request cohort, indicating that cohort has been included
ioCurrentBP.nCohortInt := 2;

// Transfer of current BP to sub system PLC
pBPC := ADR(fbSubSysToArb.i_stCurrentBP);
pBPC^ := ioCurrentBP;

///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////

TEST('Active in arbitration');
AssertTrue(fbSubSysToArb.nActiveCohort = fbSubSysToArb.nRequestCohort, 'Active cohort and request cohort do not match');
AssertTrue(fbArbiter.nActiveCohort = 1, 'Arbiter does not indicate it is included in higher arbitration');
TEST_FINISHED_NAMED('Active in arbitration');
END_METHOD

METHOD VetoFunction
VAR_INPUT
END_VAR
VAR
    nId    :    DWORD := 1;
    stReq    :    ST_BeamParams := (nTran:=12);
END_VAR
VAR_INST
    fbArbiter    :    FB_Arbiter(1);

    fbSubSysToArb : FB_SubSysToArbiter_IO; // Subsystem interface with beamline arbiter PLC
    fbArbToSubSys : FB_ArbiterToSubSys_IO;

    pBPC : POINTER TO ST_BeamParams_IO;
    pBPR : POINTER TO ST_BeamParams_IO;

    ioCurrentBP : ST_BeamParams_IO;

    FFO : FB_HardwareFFOutput; // Dummy FFO not important for these tests

END_VAR
VAR CONSTANT
    SysID : DWORD := 42;
END_VAR
// This simulates a synchronous cycle between the subsystem and arbiter PLC cycles. Ie. phase is locked, and cycle time is the same. This is not always the case, so these tests should be
// trusted with a grain of salt.

fbArbiter.AddRequest(nId, stReq,'Device'); // some device asking its local arbiter for some beam parameters


///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////

TEST('Request propagated');
    AssertTrue(fbSubSysToArb.q_stRequestedBP.nTran = stReq.nTran, 'Request not propagated');
TEST_FINISHED();

// Veto activated
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=TRUE);
//END of sub system cycle
///////////////////////////////////////

// Veto activated
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=TRUE);
//END of sub system cycle
///////////////////////////////////////

TEST('Request sustained');
    AssertTrue(fbSubSysToArb.q_stRequestedBP.nTran = PMPS_GVL.cstFullBeam.nTran, 'Request still being propagated');
    AssertTrue(fbArbiter.CheckRequest(nId), 'Request still not valid');
TEST_FINISHED();

// Veto activated
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=FALSE);
//END of sub system cycle
///////////////////////////////////////

// Veto activated
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=FALSE);
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=FALSE);
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=FALSE);
//END of sub system cycle
///////////////////////////////////////

TEST('Request restored');
    AssertTrue(fbSubSysToArb.q_stRequestedBP.nTran = stReq.nTran, 'Request still being sustained');
TEST_FINISHED();
END_METHOD
Related:

FB_SubSysToArbiter_IO

// Use on a subsystem PLC to request from the arbiter
// Run at the top of your cycle to receive the latest BP
FUNCTION_BLOCK FB_SubSysToArbiter_IO IMPLEMENTS I_HigherAuthority
VAR_INPUT
    Reset : BOOL; // Fast fault reset
    sName : STRING := 'SubSysToArbiter';
    i_bVeto : BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
    Arbiter : FB_Arbiter;
    fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR
    {attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^IO Inputs^CurrentBP'}
    i_stCurrentBP    AT %I* :    ST_BeamParams_IO;
    {attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^IO Outputs^RequestedBP'}
    q_stRequestedBP AT %Q* :    ST_BeamParams_IO;
    {attribute 'pytmc' := 'pv: TxPDO_toggle
        io: i'}
    {attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^SYNC Inputs^TxPDO toggle'}
    xTxPDO_toggle    AT     %I* :    BIT;
    {attribute 'pytmc' := 'pv: TxPDO_state
        io: i'}
    {attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^SYNC Inputs^TxPDO state'}
    xTxPDO_state    AT    %I*    :    BIT;


    // Fast faults
    ffPMPSIO_Disconnect : FB_FastFault := (i_Desc:='Arbiter network interface disconnected or not OP',i_DevName := sName);

    // A request is not considered included until the active cohort number is >= the request cohort number.
    {attribute 'pytmc' := 'pv: RequestCohort
        io: i'}
    nRequestCohort : UDINT := 0; // Request cohort
    {attribute 'pytmc' := 'pv: ActiveCohort
        io: i'}
    nActiveCohort : UDINT := 0; // Active cohort, updated by incoming BP from arbiter PLC, in the ElevateRequest arbiter call

    fbVetoArb : FB_VetoArbiter;

    fbLog : FB_LogMessage;
END_VAR
//Receiving current BP state
PMPS_GVL.stCurrentBeamParameters := IO_TO_BP(i_stCurrentBP);
PMPS_GVL.stCurrentBeamParameters.xValidToggle := xTxPDO_toggle; // This line must follow the one above. Sequence is important.

// Forwarding BP request for the subsystem
fbVetoArb.bVeto := i_bVeto;
fbVetoArb(HigherAuthority:=THIS^, LowerAuthority:=Arbiter, FFO:=fbFFHWO);

//Broadcasting current request
PMPS_GVL.stRequestedBeamParameters := IO_TO_BP(q_stRequestedBP);


q_stRequestedBP.xValid := TRUE; // This is set and held true here every cycle to prove the PLC on this side is still running

ffPMPSIO_Disconnect(
    i_xOK := xTxPDO_state = 0,
    io_fbFFHWO := fbFFHWO,
    i_xReset := Reset,
    i_DevName := sName,
    i_TypeCode := 6,
    i_xVetoable := FALSE
    );

END_FUNCTION_BLOCK

METHOD CheckRequest : BOOL
VAR_INPUT
    nReqID  : DWORD;
END_VAR
VAR_INST
    xFirstTime : BOOL := TRUE;
    nId : DWORD;
END_VAR
// Check this id matches what we've seen before
IF xFirstTime THEN
    nId := nReqId;
ELSIF nId <> nReqId THEN
    fbLog(sMsg := 'SubSysToArbiter Check mismatched with a different ID', eSevr := TcEventSeverity.Error);
    RETURN;
END_IF

nActiveCohort := ULINT_TO_UDINT(i_stCurrentBP.nCohortInt);

// i_stCurrentBP.nCohortInt is incremented by the FB_ArbiterToSubSys block on the other side of the EL669* interface
CheckRequest := nRequestCohort <= nActiveCohort;
END_METHOD

METHOD RemoveRequest : BOOL
VAR_INPUT
    nReqID    :    DWORD; //StateID to remove
END_VAR
// Update internal BP request struct
q_stRequestedBP := BP_TO_IO(PMPS_GVL.cstFullBeam);

nRequestCohort := nRequestCohort + 1;

// Mark the current cohort id
q_stRequestedBP.nCohortInt := nRequestCohort;

RemoveRequest := TRUE;
END_METHOD

METHOD RequestBP : BOOL
VAR_INPUT
    (*StateID of state requesting beam parameter set*)
    nReqID  : DWORD;
    (*Requested beam params*)
    stReqBP : ST_BeamParams;
END_VAR
// Check the request is coming from the same source we're used to

// Update internal BP request struct
q_stRequestedBP := BP_TO_IO(stReqBP);

nRequestCohort := nRequestCohort + 1;

// Mark the current cohort id
q_stRequestedBP.nCohortInt := nRequestCohort;

RequestBP := TRUE;
END_METHOD
Related:

FB_SXU

FUNCTION_BLOCK FB_SXU IMPLEMENTS I_UndulatorComplex
VAR_INPUT
    fbElectronEnergy : REFERENCE TO FB_LREALFromEPICS;
END_VAR

VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: CurrentPhotonEnergy
        io: i
        field: DESC Calculated current photon energy
        field: PREC 3
        field: EGU eV
    '}
    fCurrentPhotonEnergy : LREAL;
    {attribute 'pytmc' := '
        pv: TargetPhotonEnergy
        io: i
        field: DESC Calculated desired photon energy
        field: PREC 3
        field: EGU eV
    '}
    fTargetPhotonEnergy : LREAL;
        {attribute 'pytmc' := '
        pv: SeedUndulatorNumber
        io: i
        field: DESC Seed undulator number
    '}
    nSeedUndulator : UDINT; // Set to zero when no undulators are active
    {attribute 'pytmc' := '
        pv: TargetSeedUndulatorNumber
        io: i
        field: DESC Seed undulator number for target K
    '}
    nTargetSeedUndulator : UDINT; // Set to zero when no undulators are active
END_VAR



VAR
    // From lcls-srv01: grep -e KDes  /u1/lcls/epics/ioc/data/sioc-unds-uc*/iocInfo/IOC.pvlist |sort


    {attribute 'pytmc' := 'pv: 26; link: 2650:'}
    fbSegment_26 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 27; link: 2750:'}
    fbSegment_27 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 28; link: 2850:'}
    fbSegment_28 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 29; link: 2950:'}
    fbSegment_29 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 30; link: 3050:'}
    fbSegment_30 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 31; link: 3150:'}
    fbSegment_31 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 32; link: 3250:'}
    fbSegment_32 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 33; link: 3350:'}
    fbSegment_33 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 34; link: 3450:'}
    fbSegment_34 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 36; link: 3650:'}
    fbSegment_36 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 37; link: 3750:'}
    fbSegment_37 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 38; link: 3850:'}
    fbSegment_38 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 39; link: 3950:'}
    fbSegment_39 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 40; link: 4050:'}
    fbSegment_40 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 41; link: 4150:'}
    fbSegment_41 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 42; link: 4250:'}
    fbSegment_42 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 43; link: 4350:'}
    fbSegment_43 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 44; link: 4450:'}
    fbSegment_44 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 45; link: 4550:'}
    fbSegment_45 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 46; link: 4650:'}
    fbSegment_46 : FB_UndulatorSegment;

    {attribute 'pytmc' := 'pv: 47; link: 4750:'}
    fbSegment_47 : FB_UndulatorSegment;



    fbSegment : ARRAY [iLowBound..iHighBound] OF POINTER TO FB_UndulatorSegment;
    fbCurrentSegment : REFERENCE TO FB_UndulatorSegment;

    iIndex : UDINT;

    bInitialized : BOOL := FALSE;

END_VAR

VAR CONSTANT
    {attribute 'pytmc' := '
        pv: FirstSegment
        io: i'}
    iLowBound  : UDINT := 26;
    {attribute 'pytmc' := '
        pv: LastSegment
         io: i'}
    iHighBound : UDINT := 47;
    {attribute 'pytmc' := '
        pv: Period
        io: i
        field: EGU mm
    '}
    fPeriod_mm : LREAL := 39.0;
    {attribute 'pytmc' := '
        pv: LowK
        io: i
    '}
    fLowK : LREAL := 1.5;

    {attribute 'pytmc' := '
        pv: HiK
        io: i
    '}
    fHiK : LREAL := 5.7;
END_VAR
IF NOT bInitialized THEN
    Init();
END_IF

UndAdrUpdate();

nSeedUndulator := 0;
nTargetSeedUndulator := 0;

FOR iIndex := iLowBound TO iHighBound DO
    IF fbSegment[iIndex] <> 0 THEN
        fbCurrentSegment REF= fbSegment[iIndex]^;
        fbCurrentSegment(fbElectronEnergy:=fbElectronEnergy);

        //Mark the seed undulator, first undulator operating within K bounds
        IF fbCurrentSegment.xActive AND nSeedUndulator = 0 THEN
            nSeedUndulator := iIndex;
            fCurrentPhotonEnergy := fbCurrentSegment.fPhotonEnergyAct;
        END_IF

        IF fbCurrentSegment.xTargetActive AND nTargetSeedUndulator = 0 THEN
            nTargetSeedUndulator := iIndex;
            fTargetPhotonEnergy := fbCurrentSegment.fPhotonEnergyDes;
        END_IF
    END_IF
END_FOR

IF nSeedUndulator = 0 THEN
    fCurrentPhotonEnergy := 0;
END_IF

IF nTargetSeedUndulator = 0 THEN
    fTargetPhotonEnergy := 0;
END_IF

END_FUNCTION_BLOCK

ACTION Init:
UndAdrUpdate();

FOR iIndex := iLowBound TO iHighBound DO
        IF fbSegment[iIndex] <> 0 THEN
        fbCurrentSegment REF= fbSegment[iIndex]^;
        fbCurrentSegment.fPeriod_mm := fPeriod_mm;
        fbCurrentSegment.fLowK := fLowK;
        fbCurrentSegment.fHiK := fHiK;
    END_IF
END_FOR
bInitialized := TRUE;
END_ACTION

ACTION UndAdrUpdate:
fbSegment[26] := ADR(fbSegment_26);
fbSegment[27] := ADR(fbSegment_27);
fbSegment[28] := ADR(fbSegment_28);
fbSegment[29] := ADR(fbSegment_29);
fbSegment[30] := ADR(fbSegment_30);
fbSegment[31] := ADR(fbSegment_31);
fbSegment[32] := ADR(fbSegment_32);
fbSegment[33] := ADR(fbSegment_33);
fbSegment[34] := ADR(fbSegment_34);
fbSegment[35] := 0;
fbSegment[36] := ADR(fbSegment_36);
fbSegment[37] := ADR(fbSegment_37);
fbSegment[38] := ADR(fbSegment_38);
fbSegment[39] := ADR(fbSegment_39);
fbSegment[40] := ADR(fbSegment_40);
fbSegment[41] := ADR(fbSegment_41);
fbSegment[42] := ADR(fbSegment_42);
fbSegment[43] := ADR(fbSegment_43);
fbSegment[44] := ADR(fbSegment_44);
fbSegment[45] := ADR(fbSegment_45);
fbSegment[46] := ADR(fbSegment_46);
fbSegment[47] := ADR(fbSegment_47);
END_ACTION

PROPERTY rCurrentPhotonEnergy : REAL
VAR
END_VAR
rCurrentPhotonEnergy := fCurrentPhotonEnergy;
END_PROPERTY

PROPERTY rTargetPhotonEnergy : REAL
VAR
END_VAR
rTargetPhotonEnergy := fTargetPhotonEnergy;
END_PROPERTY
Related:

FB_TempSensor_FFO

FUNCTION_BLOCK FB_TempSensor_FFO EXTENDS FB_TempSensor
VAR_INPUT
    {attribute 'pytmc' := '
        pv: FAULT_SP
        io: input
        field: EGU C
        field: PREC 2
    '}
    fFaultThreshold : LREAL; //Faults when the threshold is reached. Trigger value.
    {attribute 'pytmc' := '
        pv: FAULT_SP_HYS
        io: input
        field: EGU %
        field: PREC 2
    '}
    fHysteresis : LREAL :=1; // percentage determining how far below the trigger value the fault should be released
    sDevName: STRING;
    bVeto : BOOL:=FALSE; // This Fault will be will not trip the beam if the bVeto is TRUE
    bAutoReset : BOOL:=TRUE;
END_VAR
VAR_IN_OUT
    io_fbFFHWO    :    FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
END_VAR
VAR
    {attribute 'instance-path'}
    {attribute 'noinit'}
    sPath: STRING;
    bFAULT_OK:BOOL :=FALSE;
    FFO    :    FB_FastFault :=(
        i_Desc := 'Fault occurs when the temprature trip point is reached',
        i_TypeCode := 16#f700);
    rtRESET :       R_TRIG;
    ftFAULT :       F_TRIG;
    ftConnected     :       F_TRIG;
    fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.MPS, nMinTimeViolationAcceptable:=10);
END_VAR
// The manual states that we are disconnected if we are both overrange and in an error state
bConnected := NOT (bOverrange AND bError);
fTemp := INT_TO_LREAL(iRaw) * fResolution;

//Verify Hysteresis is between 1-100, Shouldn't be 0. shouldn't be a 100 either.
fHysteresis:=  LIMIT(1, fHysteresis, 100);

// Evaluate the threshold trip point
IF (fTemp >= fFaultThreshold) THEN
    bFAULT_OK := FALSE;
ELSIF (fTemp < (fFaultThreshold - fFaultThreshold*fHysteresis/100)) THEN
    bFAULT_OK := TRUE;
END_IF
bFAULT_OK R= NOT bConnected;


ACT_Logger();

(*FAST FAULT*)
FFO(i_xOK := bFAULT_OK OR bVeto,
    i_xReset :=,
    i_xAutoReset := bAutoReset,
    i_DevName := sDevName,
    io_fbFFHWO := io_fbFFHWO);

END_FUNCTION_BLOCK

ACTION ACT_Logger:
ftFAULT(CLK:= FFO.i_xOK);
rtRESET(CLK:=FFO.i_xOK);
ftConnected(CLK:= bConnected);

IF(ftConnected.Q) THEN
    fbLogger(sMsg := 'Sensor Connection Fault, beam off', eSevr:=TcEventSeverity.Critical);
END_IF

IF(ftFAULT.Q) THEN
    fbLogger(sMsg := 'Temp Threshold Fault, beam off', eSevr:=TcEventSeverity.Critical);
END_IF

IF(rtRESET.Q) THEN
    fbLogger(sMsg := 'Temp Threshold Fault condition clear', eSevr:=TcEventSeverity.Info);
END_IF
END_ACTION
Related:

FB_UndulatorSegment

FUNCTION_BLOCK FB_UndulatorSegment
VAR_INPUT
    (* Undulator period in millimeters, to be set by subclasses *)
    fPeriod_mm : LREAL := 1.0;
    fbElectronEnergy : REFERENCE TO FB_LREALFromEPICS;

    fLowK : LREAL := 0;
    fHiK : LREAL := 6;
    fKRangeHyst : LREAL := 0.01;
END_VAR

VAR_OUTPUT
    {attribute 'pytmc' := '
        pv: eVAct
        field: DESC Calculated photon energy
        field: PREC 3
        field: EGU eV
    '}
    fPhotonEnergyAct : LREAL;

    {attribute 'pytmc' := '
        pv: eVDes
        field: DESC Calculated desired photon energy
        field: PREC 3
        field: EGU eV
    '}
    fPhotonEnergyDes : LREAL;

    {attribute 'pytmc' := '
        pv: Active
        io: i
        field: DESC Undulator is considered active
    '}
    xActive : BOOL; // Undulator is considered active

    {attribute 'pytmc' := '
        pv: TargetActive
        io: i
        field: DESC Target K would make und active
    '}
    xTargetActive : BOOL; // Undulator is considered active at this target

    {attribute 'pytmc' := '
        pv: KAct
        io: i
        field: DESC Current K
    '}
    fKAct : LREAL;

    {attribute 'pytmc' := '
        pv: KDes
        io: i
        field: DESC Target K
    '}
    fKDes : LREAL;

        {attribute 'pytmc' := '
        pv: KActValid
        io: i
        field: DESC Current K Readback Valid
    '}
    bKActValid : BOOL;

    {attribute 'pytmc' := '
        pv: KDesValid
        io: i
        field: DESC Target K Readback Valid
    '}
    bKDesValid : BOOL;

END_VAR

VAR
    {attribute 'pytmc' := '
        pv: KDes
        link: KDes
    '}
    fbKDesired : FB_LREALFromEPICS;

    {attribute 'pytmc' := '
        pv: KAct
        link: KAct
    '}
    fbKActual : FB_LREALFromEPICS;

END_VAR
fbKDesired();
fbKActual();

fKAct := fbKActual.fValue;
bKActValid := fbKActual.bValid;
fKDes := fbKDesired.fValue;
bKDesValid := fbKDesired.bvalid;

IF __ISVALIDREF(fbElectronEnergy) THEN

    IF fbKActual.bValid AND fbElectronEnergy.bValid THEN
        fPhotonEnergyAct := F_CalculatePhotonEnergy(
            fElectronEnergy_GeV:=fbElectronEnergy.fValue,
            fUndulatorPeriod_mm:=fPeriod_mm,
            fUndulatorStrength:=fbKActual.fValue
        );

        //Set this undulator active if actual K is within operational range
        xActive S= fLowK <= fbKActual.fValue AND fbKActual.fValue <= fHiK AND
        (fbKActual.bValid AND fbKActual.bValid);
        xActive R= (fLowK - fKRangeHyst) > fbKActual.fValue OR fbKActual.fValue > (fHiK + fKRangeHyst) OR
                    (NOT fbKActual.bValid OR NOT fbKActual.bValid);

        //Set this undulator active if target K is within operational range
        xTargetActive S= fLowK <= fbKDesired.fValue AND fbKDesired.fValue <= fHiK AND
        (fbKDesired.bValid AND fbKDesired.bValid);
        xTargetActive R= (fLowK - fKRangeHyst) > fbKDesired.fValue OR fbKDesired.fValue > (fHiK + fKRangeHyst) OR
                    (NOT fbKDesired.bValid OR NOT fbKDesired.bValid);
    END_IF

    IF fbKDesired.bValid AND fbElectronEnergy.bValid THEN
        fPhotonEnergyDes := F_CalculatePhotonEnergy(
            fElectronEnergy_GeV:=fbElectronEnergy.fValue,
            fUndulatorPeriod_mm:=fPeriod_mm,
            fUndulatorStrength:=fbKDesired.fValue
        );
    END_IF

END_IF

END_FUNCTION_BLOCK
Related:

FB_VetoArbiter

FUNCTION_BLOCK FB_VetoArbiter IMPLEMENTS I_HigherAuthority
VAR_INPUT
    bVeto : BOOL := FALSE; // Rising edge clears request, hold true to veto continuously, falling edge restores request
    HigherAuthority : I_HigherAuthority; // Typically connected to a higher-level arbiter.
    LowerAuthority : I_LowerAuthority; // Lower authority to be vetoed
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
    FFO : FB_HardwareFFOutput; // This should be the FFO upstream of the veto device
END_VAR
VAR
    ffKeepItSecretKeepItSafe : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_Desc := 'Holds beam off until request is back in arbitration',
        i_TypeCode := 200,
        i_xVetoable := TRUE
    );

    stStandbyBP : ST_BeamParams;


    rtVeto : R_TRIG;
    ftVeto : F_TRIG;
END_VAR
rtVeto(CLK:=bVeto);
ftVeto(CLK:=bVeto);

IF rtVeto.Q THEN
    HigherAuthority.RemoveRequest(LowerAuthority.nLowerAuthorityID);
    HigherAuthority.RequestBP(LowerAuthority.nLowerAuthorityID, PMPS_GVL.cstFullBeam);
ELSIF ftVeto.Q THEN
    HigherAuthority.RemoveRequest(LowerAuthority.nLowerAuthorityID);
    HigherAuthority.RequestBP(LowerAuthority.nLowerAuthorityID, stStandbyBP);
END_IF

LowerAuthority.ElevateRequest(THIS^);

//Fast fault that holds beam off until the request is added back into the system
// when bVeto goes false.
ffKeepItSecretKeepItSafe.i_xOK := HigherAuthority.CheckRequest(LowerAuthority.nLowerAuthorityID) OR bVeto;
ffKeepItSecretKeepItSafe(io_fbFFHWO:=FFO);

END_FUNCTION_BLOCK

METHOD CheckRequest : BOOL
VAR_INPUT
    nReqID  : DWORD;
END_VAR
IF bVeto THEN
    CheckRequest := TRUE;
ELSE
    CheckRequest := HigherAuthority.CheckRequest(nReqID);
END_IF
END_METHOD

METHOD RemoveRequest : BOOL
VAR_INPUT
    (*StateID to remove*)
    nReqID  : DWORD;
END_VAR
IF bVeto THEN
    RemoveRequest := TRUE;
ELSE
    RemoveRequest := HigherAuthority.RemoveRequest(nReqID);
END_IF
END_METHOD

METHOD RequestBP : BOOL
VAR_INPUT
    (*StateID of state requesting beam parameter set*)
    nReqID  : DWORD;
    (*Requested beam params*)
    stReqBP : ST_BeamParams;
END_VAR
IF NOT bVeto THEN
    // Pass request along to higher authority
    RequestBP := HigherAuthority.RequestBP(nReqID, stReqBP);
ELSE
    RequestBP := TRUE;
END_IF

IF RequestBP THEN
    stStandbyBP := stReqBP;
END_IF
END_METHOD
Related:

FB_VetoArbiter_Test

{attribute 'call_after_init'}
FUNCTION_BLOCK FB_VetoArbiter_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
VAR CONSTANT
    ArbID : DWORD := 1;
    HigherArbID : DWORD := 2;
END_VAR
VetoArbiter();

END_FUNCTION_BLOCK

METHOD VetoArbiter

VAR_INPUT
END_VAR
VAR
    nId    :    DWORD := 1;
    nId2 : DWORD := 10;
    stReq    :  ST_BeamParams := (nTran:=0.12);
    stReq2  :       ST_BeamParams := (nTran:=0.10);
END_VAR
VAR_INST
    fbArbiter    :    FB_Arbiter(1);

    fbHigherArb : FB_Arbiter(2);

    ArbBP : ST_BeamParams:=(nTran:=1);

    VetoArb : FB_VetoArbiter;

    FFO : FB_HardwareFFOutput;

    fbHA : FB_DummyHA;

END_VAR
TEST('VetoNotIn');
fbArbiter.AddRequest(nReqID:=nId, stReqBP:=stReq, sDevName :='Device');

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb,
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb,
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

AssertTrue(fbHigherArb.q_stBeamParams.nTran = stReq.nTran, 'We should see the transmission number here, veto device is not in');

TEST_FINISHED();

TEST('VetoIn');
// Veto device is in
// This should effectively remove the fbArbiter request from the higher arbiter pool

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := TRUE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := TRUE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

AssertTrue(fbHigherArb.q_stBeamParams.nTran = PMPS_GVL.cstFullBeam.nTran, 'Veto device is in, trans should be restored to 100');
AssertTrue(fbArbiter.CheckRequest(nId), 'Request should still be considered valid (1)');
AssertFalse(fbHigherArb.CheckRequest(ArbID), 'Lower arb request should be gone from the higher pool');

TEST_FINISHED();

TEST('AnotherRequest');
// Another request is added, should be approved immediately, and with no effect on the final set
fbArbiter.RequestBP(nReqID := nId2, stReqBP:=stReq2);

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := TRUE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := TRUE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

AssertTrue(ArbBP.nTran = PMPS_GVL.cstFullBeam.nTran, 'Veto device remained in, trans should still be 100');
AssertTrue(fbArbiter.CheckRequest(nId), 'Request should still be considered valid (2)');
AssertTrue(fbArbiter.CheckRequest(nId2), '2nd Request should be considered valid');

TEST_FINISHED();

TEST('RemoveVeto');
// Removal of veto device, should produce a fault
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

AssertFalse(VetoArb.ffKeepItSecretKeepItSafe.i_xOK, 'Should produce a fast fault at this point because the request is not yet back in the pool');

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);

VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
    LowerAuthority:=fbArbiter,
    FFO:=FFO);

AssertTrue(VetoArb.ffKeepItSecretKeepItSafe.i_xOK, 'Fault should be gone.');

TEST_FINISHED();
END_METHOD
Related:

FB_VetoDevice

// Relays veto device state, updates current BP and sends a message when veto state changes.
FUNCTION_BLOCK FB_VetoDevice
VAR_INPUT
    i_bIn : BOOL;
    i_bOut : BOOL;
END_VAR
VAR_OUTPUT
    q_bIn : BOOL;
    q_bOut : BOOL;
END_VAR
VAR_IN_OUT
    stCurrentBP : ST_BeamParams;
END_VAR
VAR

    VetoDevice_IN : UINT := PMPS_GVL.MAX_VETO_DEVICES; // Veto device state array index
    VetoDevice_OUT : UINT := PMPS_GVL.MAX_VETO_DEVICES; // Veto device state array index

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

    // Logging
        fbLogMsg : FB_LogMessage := (
            eSubSystem := E_Subsystem.MPS);

        rtIn : R_TRIG;
        rtOut : R_TRIG;
    ////////////////////////////////

    bInit: BOOL := TRUE;
END_VAR
IF bInit THEN
    fbLogMsg.sJson := F_PMPS_JSON(VetoDeviceName, sPath, 1000);
    bInit := FALSE;
END_IF

// Log
/////////////////////////////
    rtIn(CLK := i_bIn);
    rtOut(CLK := i_bOut);

    IF rtIn.Q THEN
        fbLogMsg.sMsg := CONCAT(VetoDeviceName, ' moved IN');
        fbLogMsg();
    ELSIF rtOut.Q THEN
        fbLogMsg.sMsg := CONCAT(VetoDeviceName, ' moved OUT');
        fbLogMsg();
    END_IF

// Relay
///////////////////////
    q_bIn := i_bIn;
    q_bOut := i_bOut;

// Update current beam parameters
/////////////////////////////////////
    stCurrentBP.aVetoDevices[VetoDevice_IN] := i_bIn;
    stCurrentBP.aVetoDevices[VetoDevice_OUT] := i_bOut;

END_FUNCTION_BLOCK
Related:

IO_TO_APT

FUNCTION IO_TO_APT : ST_PMPS_Aperture
VAR_INPUT
    IO : ST_PMPS_Aperture_IO;
END_VAR
VAR
END_VAR
IO_TO_APT.Height := IO.Height;
IO_TO_APT.Width := IO.Width;
IO_TO_APT.xOK := IO.xOK;

END_FUNCTION
Related:

IO_TO_ATT

FUNCTION IO_TO_ATT : ST_PMPS_Attenuator
VAR_INPUT
    IO : ST_PMPS_Attenuator_IO;
END_VAR
VAR
END_VAR
IO_TO_ATT.nTran := IO.nTran;
IO_TO_ATT.xAttOK := IO.xAttOK;

END_FUNCTION
Related:

IO_TO_BP

{attribute 'no_check'}
FUNCTION IO_TO_BP : ST_BeamParams
VAR_INPUT
    IO : ST_BeamParams_IO;
END_VAR
VAR
    idx : UINT;
END_VAR
FOR idx := 1 TO PMPS_GVL.AUX_ATTENUATORS DO
    IO_TO_BP.astAttenuators[idx] := IO_TO_ATT(IO.astAttenuators[idx]);
END_FOR

FOR idx := 1 TO PMPS_GVL.MAX_APERTURES DO
    IO_TO_BP.astApertures[idx] := IO_TO_APT(IO.astApertures[idx]);
END_FOR

IO_TO_BP.aVetoDevices := IO.aVetoDevices;
IO_TO_BP.nTran := IO.nTran;
IO_TO_BP.nCohortInt := ULINT_TO_UDINT(IO.nCohortInt);
IO_TO_BP.neVRange := IO.neVRange;
IO_TO_BP.neV := IO.neV;
IO_TO_BP.nBCRange := IO.nBCRange;
IO_TO_BP.nBeamClass := IO.nBeamClass;
IO_TO_BP.nMachineMode := IO.nMachineMode;
IO_TO_BP.nRate := IO.nRate;
IO_TO_BP.xValid := IO.xValid;
IO_TO_BP.xValidToggle := IO.xValidToggle;

END_FUNCTION
Related:

MAIN

PROGRAM MAIN
VAR
    fbSetPERanges : PE_Ranges;

    fbDiffBPTest    :    FB_DiffBP_Test;
    fbBPTMTest    :    FB_BPTM_Test;
    fbSafeBPCompareTest    :    FB_SafeBPCompare_Test;
    ////fbevWithinSpecTest    :    FB_evWithinSpec_Test;---
    ////fbPEWTest    :    FB_PhotonEnergyWatcher_Test;---
    fbFFTest    :    FB_FastFault_Test;
    fbArbiterTest    :    FB_Arbiter_Test;
    fbVetoArbiterTest : FB_VetoArbiter_Test;
    fbevRangeCalcTest : FB_evRangeCalculator_Test;
    fbSubSysToArbTest : FB_SubsysToArb_Test;
    fbArbToSubSysTest : FB_ArbToSubsys_Test;
END_VAR
TcUnit.RUN();

END_PROGRAM
Related:

PE_Ranges

// Does nothing other than set the gvl for photon energy bitmask to one of two constants, K or L.
// Workaround for compile defines not fully working for libraries at the time of writing this.
// Otherwise I would have just used the compile define in the GVL declaration.
FUNCTION_BLOCK PE_Ranges
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR


END_FUNCTION_BLOCK

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
END_VAR
{IF defined (L)}
    PMPS_GVL.g_areVBoundaries := PMPS_GVL.g_areVBoundariesL;
{ELSIF defined (K)}
    PMPS_GVL.g_areVBoundaries := PMPS_GVL.g_areVBoundariesK;
{END_IF}
END_METHOD
Related: