DUTs

DUT_01_Channel_NW

TYPE DUT_01_Channel_NW :
STRUCT
    header: DUT_Header_NW;
    channel1: DUT_Channel_NW;
END_STRUCT
END_TYPE
Related:

DUT_Channel_NW

TYPE DUT_Channel_NW :
STRUCT
    count: DWORD;                       // 4    4
    timing: DWORD;                      // 4    8
    scale: WORD;                        // 2    10
    hardwareID: ARRAY [0..15] OF BYTE;  // 16   26
    reserved1: BYTE;                    // 1    27
    channel: BYTE;                      // 1    28
    error: BYTE;                        // 1    29
    mode: BYTE;                         // 1    30
    scale_denominator: WORD;            // 2    32
END_STRUCT
END_TYPE
(*
typedef struct {
    uint32_t    encoderValue;       // network byte order
    uint32_t    timing;             // network byte order
    uint16_t    scale;              // network byte order
    char        hardwareID[16];
    uint8_t     reserved1;
    uint8_t     channel;
    uint8_t     error;
    uint8_t     mode;
    uint16_t     scale_denominator; // network byte order
} encoder_channel_t;
*)

DUT_Header_NW

TYPE DUT_Header_NW :
STRUCT
    frameCount: WORD;                   // 2    2
    reserved1: WORD;                    // 2    4
    major: WORD;                        // 2    6
    minor: BYTE;                        // 1    7
    micro: BYTE;                        // 1    8
    hardwareID: ARRAY [0..15] OF BYTE;  // 16   24
    reserved2: BYTE;                    // 1    25
    channelMask: BYTE;                  // 1    26
    errorMask: BYTE;                    // 1    27
    mode: BYTE;                         // 1    28
    reserved3: DWORD;                   // 4    32
END_STRUCT
END_TYPE
(*
typedef struct {
    uint16_t    frameCount;         // network byte order
    uint8_t     reserved1[2];
    uint16_t    majorVersion;       // network byte order
    uint8_t     minorVersion;
    uint8_t     microVersion;
    char        hardwareID[16];
    uint8_t     reserved2;
    uint8_t     channelMask;
    uint8_t     errorMask;
    uint8_t     mode;
    uint8_t     reserved3[4];
} encoder_header_t;
*)

E_B4C_Rh_CoatingStates

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_B4C_Rh_CoatingStates :
(
    Unknown := 0,
    B4C := 1,
    Rh := 2
) UINT;
END_TYPE

E_Grating_States

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_Grating_States :
(
    Unknown := 0,
    LRG := 1,
    Unruled := 2,
    YAG := 3,
    MEG := 4,
    HEG := 5,
    LEG := 6
) UINT;
END_TYPE

E_MR1K1_States

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_MR1K1_States :
(
    Unknown := 0,
    B4C := 1,
    OUT := 2
) UINT;
END_TYPE

ENUM_XS_YAG_States

{attribute 'qualified_only'}
TYPE ENUM_XS_YAG_States :
(
    Unknown := 0,
    OUT := 1,
    YAG1 := 2,
    YAG2 := 3
);
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_rix_optics : ST_LibVersion := (iMajor := 3, iMinor := 7, iBuild := 0, iRevision := 0, nFlags := 1, sVersion := '3.7.0');
END_VAR

GVL_COM_Buffers

VAR_GLOBAL
    // M1K2
    Serial_RXBuffer_M1K2 : ComBuffer;
    Serial_TXBuffer_M1K2 : ComBuffer;
END_VAR

GVL_M1K1

{attribute 'qualified_only'}
VAR_GLOBAL
    // Pitch Mechanism:\
    // Currently unused
    {attribute 'TcLinkTo' := '.diEncCnt:=TIIB[EL5042_M1K2_Pitch]^FB Inputs Channel 1^Position'}
    M1K1_Pitch : HOMS_PitchMechanism := (ReqPosLimHi := 24681,
                                         ReqPosLimLo := 24321,
                                         diEncPosLimHi := 10139808,
                                         diEncPosLimLo := 9950984); // Set to 50 urad before hard limits tripped during SAT
END_VAR

GVL_M1K1_BENDER_Constants

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    // Encoder reference values in counts = nm
    nM1K1bendUS_ENC_REF : ULINT := 21458400;
    nM1K1bendDS_ENC_REF : ULINT := 21225900;

    // PMPS Limits for benders
    nM1K1bendUS_PMPS_UpperLimit : ULINT := 22100000; //21500000;//22100000; As requested by Alex R and RP, switched back again
    nM1K1bendUS_PMPS_LowerLimit : ULINT := 20900000; //20100000;//20900000;

    nM1K1bendDS_PMPS_UpperLimit : ULINT := 21800000;//21100000;//21800000;
    nM1K1bendDS_PMPS_LowerLimit : ULINT := 20600000;// 19800000;//20600000;
END_VAR

GVL_M1K1_BENDER_RTD

{attribute 'qualified_only'}
VAR_GLOBAL
    // M1K1 US RTDs
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M1K1US1_M1K1US2]^RTD Inputs Channel 1^Value'}
    nM1K1US_RTD_1 AT %I* : INT;
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M1K1US1_M1K1US2]^RTD Inputs Channel 2^Value'}
    nM1K1US_RTD_2 AT %I* : INT;
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M1K1US3_M1K1DS1]^RTD Inputs Channel 1^Value'}
    nM1K1US_RTD_3 AT %I* : INT;

    // M1K1 DS RTDs
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M1K1US3_M1K1DS1]^RTD Inputs Channel 2^Value'}
    nM1K1DS_RTD_1 AT %I* : INT;
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M1K1DS2_M1K1DS3]^RTD Inputs Channel 1^Value'}
    nM1K1DS_RTD_2 AT %I* : INT;
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M1K1DS2_M1K1DS3]^RTD Inputs Channel 2^Value'}
    nM1K1DS_RTD_3 AT %I* : INT;

END_VAR

GVL_M1K1_Constants

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    // Encoder reference values in counts = nm
    // Enc reference values from Axilon FAT
    nYUP_ENC_REF : ULINT := 13008545;
    nYDWN_ENC_REF : ULINT := 11158257;
    nXUP_ENC_REF : ULINT := 19831295;
    nXDWN_ENC_REF : ULINT := 19604132;

    (* previous values
    nXUP_ENC_REF : ULINT := 19649910;
    nXDWN_ENC_REF : ULINT := 19609100;
    *)
END_VAR

GVL_M1K2

{attribute 'qualified_only'}
VAR_GLOBAL
    // Pitch Mechanism:
    // Currently Unused
    {attribute 'TcLinkTo' := '.diEncCnt:=TIIB[EL5042_M1K2_Pitch]^FB Inputs Channel 1^Position'}
    M1K2_Pitch : HOMS_PitchMechanism := (ReqPosLimHi := 200,
                                         ReqPosLimLo := -30,
                                         diEncPosLimHi := 10121210,
                                         diEncPosLimLo := 10004610); // Set to 50 urad before hard limits tripped during SAT
END_VAR

GVL_M1K2_Constants

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    // Encoder reference values in counts = nm
    // Enc reference values after alignment 3-13-20
    nYLEFT_ENC_REF : ULINT := 99171678;
    nYRIGHT_ENC_REF : ULINT := 101371326;
    nXUP_ENC_REF : ULINT := 19501048;
    nXDWN_ENC_REF : ULINT := 20872028;

    // Lever arm for Yright/Yleft -> Roll about Z-axis
    fRollLeverArm_um : REAL := 717000; // lever arm for Yright/Yleft axes in um
END_VAR

GVL_M2K2

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    // Encoder reference values in counts = nm
    // Put encoder reference values for nominal 0 here
    nM2K2X_ENC_REF : ULINT := 6077000; //these numbers
    nM2K2Y_ENC_REF : ULINT := 5975460; //are
    nM2K2rX_ENC_REF : ULINT := 50412360;//real
END_VAR

GVL_M3K2

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    // Encoder reference values in counts = nm
    // Put encoder reference values for nominal 0 here
    nM3K2X_ENC_REF : ULINT := 5951195;
    nM3K2Y_ENC_REF : ULINT := 5857203;
    nM3K2rY_ENC_REF : ULINT := 39991700;
    nM3K2US_ENC_REF : ULINT := 1234567; //these numbers
    nM3K2DS_ENC_REF : ULINT := 1234567;      //are not real!!

(*
    // PMPS Limits for benders - need to identify these
    nM3K2US_PMPS_UpperLimit : ULINT := 21500000;//22100000;
    nM3K2US_PMPS_LowerLimit : ULINT := 20100000;//20900000;

    nM3K2DS_PMPS_UpperLimit : ULINT := 21100000;//21800000;
    nM3K2DS_PMPS_LowerLimit : ULINT := 19800000;//20600000;
*)
END_VAR

VAR_GLOBAL
    // M3K2 US RTDs
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M3K2US1_M3K2US2]^RTD Inputs Channel 1^Value'}
    nM3K2US_RTD_1 AT %I* : INT;
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M3K2US1_M3K2US2]^RTD Inputs Channel 2^Value'}
    nM3K2US_RTD_2 AT %I* : INT;


    // M3K2 DS RTDs
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M3K2US3_M3K2DS1]^RTD Inputs Channel 2^Value'}
    nM3K2DS_RTD_1 AT %I* : INT;
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M3K2DS2_M3K2DS3]^RTD Inputs Channel 2^Value'}
    nM3K2DS_RTD_3 AT %I* : INT;

END_VAR

GVL_M4K2

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    // Encoder reference values in counts = nm
    // Put encoder reference values for nominal 0 here
    nM4K2X_ENC_REF : ULINT := 5902073;
    nM4K2Y_ENC_REF : ULINT := 6000313;
    nM4K2rX_ENC_REF : ULINT := 50657020;
    nM4K2US_ENC_REF : ULINT := 1234567; //these numbers
    nM4K2DS_ENC_REF : ULINT := 1234567; //are not real!!

    // PMPS Limits for benders - need to identify these
    nM4K2US_PMPS_UpperLimit : ULINT := 21500000;//22100000;
    nM4K2US_PMPS_LowerLimit : ULINT := 20100000;//20900000;

    nM4K2DS_PMPS_UpperLimit : ULINT := 21100000;//21800000;
    nM4K2DS_PMPS_LowerLimit : ULINT := 19800000;//20600000;
END_VAR

VAR_GLOBAL
    // M4K2 US RTDs
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M4K2US1_M4K2US2]^RTD Inputs Channel 1^Value'}
    nM4K2US_RTD_1 AT %I* : INT;
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M4K2US1_M4K2US2]^RTD Inputs Channel 2^Value'}
    nM4K2US_RTD_2 AT %I* : INT;
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M4K2US3_M4K2DS1]^RTD Inputs Channel 1^Value'}
    nM4K2US_RTD_3 AT %I* : INT;

    // M4K2 DS RTDs
    {attribute 'TcLinkTo' := 'TIIB[EL3202-0010_M4K2US3_M4K2DS1]^RTD Inputs Channel 2^Value'}
    nM4K2DS_RTD_1 AT %I* : INT;


END_VAR

GVL_PMPS

{attribute 'qualified_only'}
VAR_GLOBAL
    {attribute 'pytmc' := 'pv: PLC:RIX:OPTICS:ARB:01'}
    fbArbiter1: FB_Arbiter(1);
    {attribute 'pytmc' := 'pv: PLC:RIX:OPTICS:ARB:02'}
    fbArbiter2: FB_Arbiter(2);
    {attribute 'pytmc' := 'pv: PLC:RIX:OPTICS:FFO:01'}
    {attribute 'TcLinkTo' := '.q_xFastFaultOut:=TIIB[PMPS_FFO]^Channel 1^Output'}
    fbFastFaultOutput1: FB_HardwareFFOutput := (bAutoReset := TRUE , i_sNetID:='172.21.42.126.1.1' );
    {attribute 'pytmc' := 'pv: PLC:RIX:OPTICS:FFO:02'}
    {attribute 'TcLinkTo' := '.q_xFastFaultOut:=TIIB[PMPS_FFO]^Channel 2^Output'}
    fbFastFaultOutput2: FB_HardwareFFOutput := (bAutoReset := TRUE, i_sNetID:='172.21.42.126.1.1');
    rPhotonEnergy: REAL;
END_VAR

GVL_SerialIO

VAR_GLOBAL
    //Better have your inputs and outputs!
    // M1K2
    Serial_stComIn_M1K2   AT %I*    :       EL6inData22B (*KL6inData22B*);
    Serial_stComOut_M1K2  AT %Q*    :       EL6outData22B (*KL6outData22B*);
END_VAR

GVL_States

{attribute 'qualified_only'}
VAR_GLOBAL
    stDefaultOffsetY : ST_PositionState := (
        fDelta:=2000,
        fVelocity:=100,
        fAccel:=5050,
        fDecel:=5050,
        bMoveOk:=TRUE,
        bValid:=TRUE,
        bUseRawCounts:=TRUE
    );
        stDefaultOffsetX : ST_PositionState := (
        fDelta:=10000,
        fVelocity:=150,
        fAccel:=1000,
        fDecel:=1000,
        bMoveOk:=TRUE,
        bValid:=TRUE,
        bUseRawCounts:=TRUE
    );
        stDefaultKBX : ST_PositionState := (
        fDelta:=5,
        fVelocity:=0.3,
        fAccel:=100,
        fDecel:=100,
        bMoveOk:=TRUE,
        bValid:=TRUE,
        bUseRawCounts:=TRUE
    );
        stDefaultKBY : ST_PositionState := (
        fDelta:=5,
        fVelocity:=0.3,
        fAccel:=10,
        fDecel:=10,
        bMoveOk:=TRUE,
        bValid:=TRUE,
        bUseRawCounts:=TRUE
    );
END_VAR

Main

VAR_GLOBAL
(*
    Only includes motor definitions for the IOC
    These are hard-coded to be Main.M#,
    but are very convenient to store in a GVL,
    hence the confusing namespace here

    This should be refactored once the IOC
    supports arbitrary ads paths for motors
*)
    (*MR1K2*)
    // Motors
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K2_Yleft]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_M1K2_Yleft]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M1K2_Yleftright]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:MMS:YLEFT
    '}
    M1 : ST_MotionStage := (fVelocity:=100.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=False); // M1K2 Yleft
    fbMotionStage_m1 : FB_MotionStage;

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K2_Yright]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_M1K2_Yright]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M1K2_Yleftright]^FB Inputs Channel 2^Position'}
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:MMS:YRIGHT
    '}
    M2 : ST_MotionStage := (fVelocity:=100.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=True); // M1K2 Yright
    fbMotionStage_m2 : FB_MotionStage;
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K2_Xup]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_M1K2_Xup]^STM Status^Status^Digital input 2;
                               .nRawEncoderULINT:=TIIB[EL5042_M1K2_Xupdwn]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:MMS:XUP
    '}
    M3 : ST_MotionStage := (fVelocity:=150.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=True); // M1K2 Xup
    fbMotionStage_m3 : FB_MotionStage;

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K2_Xdwn]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_M1K2_Xdwn]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M1K2_Xupdwn]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:MMS:XDWN
    '}
    M4 : ST_MotionStage := (fVelocity:=150.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=True); // M1K2 Xdwn
    fbMotionStage_m4 : FB_MotionStage;

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K2_PitchCoarse]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_M1K2_PitchCoarse]^STM Status^Status^Digital input 2'}
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:MMS:PITCH
    '}
    M5 : ST_MotionStage := (fVelocity:=30.0, nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, bPowerSelf:=True); // M1K2 Pitch Stepper


    (*SP1K1-Mono*)
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:MMS:M_PI
    '}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[m_pi_m]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[m_pi_m]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[m_pi_up_dwn_e]^FB Inputs Channel 2^Position'}
    M6: ST_MotionStage:=(nEnableMode:=ENUM_StageEnableMode.ALWAYS, fVelocity:=200.0, bPowerSelf:=True); // M_PI, urad
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:MMS:G_PI
    '}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[g_pi_m]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[g_pi_m]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[g_pi_up_dwn_e]^FB Inputs Channel 1^Position'}
    M7: ST_MotionStage:=(nEnableMode:=ENUM_StageEnableMode.ALWAYS, fVelocity:=200.0, bPowerSelf:=True); // G_PI, urad
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:MMS:M_H
    '}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[m_h_m]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[m_h_m]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[m_h_e-g_h_e]^FB Inputs Channel 1^Position'}
    M8: ST_MotionStage:=(nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, fVelocity:=500.0, bPowerSelf:=True); // M_H, um
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:MMS:G_H
    '}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[g_h_m]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[g_h_m]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[m_h_e-g_h_e]^FB Inputs Channel 2^Position'}
    M9: ST_MotionStage:=(nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, fVelocity:=1000.0, bPowerSelf:=False); // G_H, um
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:MMS:SD_V
    '}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[s_io_m]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[s_io_m]^STM Status^Status^Digital input 2'}
    M10: ST_MotionStage:=(nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, fVelocity:=500.0, bPowerSelf:=True); // SD_V, um
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:MMS:SD_ROT
    '}
    // no limits on this motion
    M11: ST_MotionStage:=(nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, fVelocity:=500.0, bPowerSelf:=True); // SD_R, urad

    (*MR1K1*)
    // Should move before MR1K2 and re-number each motor, lots of work
    // need to fix Axis IDs for IOC to work
    // For now just want functional PLC project
    // Motors

    {attribute 'TcLinkTo' := '.bLimitBackwardEnable:=TIIB[EL7047_M1K1_Ydwn]^STM Status^Status^Digital input 2;
                              .bLimitForwardEnable:=TIIB[EL7047_M1K1_Ydwn]^STM Status^Status^Digital input 1;
                              .nRawEncoderULINT:=TIIB[EL5042_M1K1_Yupdwn]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:MMS:YUP
    '}
    M12 : ST_MotionStage := (fVelocity:=100.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=False); // M1K1 Yup
    fbMotionStage_m12 : FB_MotionStage;
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K1_Ydwn]^STM Status^Status^Digital input 1;
                        .bLimitBackwardEnable:=TIIB[EL7047_M1K1_Ydwn]^STM Status^Status^Digital input 2'}
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:MMS:YDWN
    '}
    M13 : ST_MotionStage := (fVelocity:=100.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=True); // M1K1 Ydwn
    fbMotionStage_m13 : FB_MotionStage;

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K1_Xup]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_M1K1_Xup]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M1K1_Xupdwn]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:MMS:XUP
    '}
    M14 : ST_MotionStage := (fVelocity:=150.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=False); // M1K1 Xup
    fbMotionStage_m14 : FB_MotionStage;

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K1_Xdwn]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_M1K1_Xdwn]^STM Status^Status^Digital input 2'}
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:MMS:XDWN
    '}
    M15 : ST_MotionStage := (fVelocity:=150.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=True); // M1K1 Xdwn
    fbMotionStage_m15 : FB_MotionStage;

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_M1K1_PitchCoarse]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_M1K1_PitchCoarse]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M1K1_Pitch]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:MMS:PITCH
    '}
    M16 : ST_MotionStage := (fVelocity:=30.0, nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=True); // M1K1 Pitch Stepper

    {attribute 'TcLinkTo' := '.bLimitBackwardEnable:=TIIB[EL7041_M1K1_BEND_US]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT    := TIIB[EL5042_M1K1_BEND_USDS]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
    pv: MR1K1:BEND:MMS:US
    '}
    // unlinking this .bLimitForwardEnable :=TIIB[EL7041_M1K1_BEND_US]^STM Status^Status^Digital input 1;
    M17 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, bPowerSelf:=TRUE); //MR1K1 US BEND
    fbMotionStage_m17 : FB_MotionStage;

    {attribute 'TcLinkTo' := '.bLimitForwardEnable :=TIIB[EL7041_M1K1_BEND_DS]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M1K1_BEND_DS]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT    := TIIB[EL5042_M1K1_BEND_USDS]^FB Inputs Channel 2^Position'}
    {attribute 'pytmc' := '
    pv: MR1K1:BEND:MMS:DS
    '}
    M18 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, bPowerSelf:=TRUE); //MR1K1 DS BEND
    fbMotionStage_m18 : FB_MotionStage;

    (*SL1K2*)
    // Motors
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_SL1K2_PITCH]^STM Status^Status^Digital input 2;
                              .bLimitBackwardEnable:=TIIB[EL7047_SL1K2_PITCH]^STM Status^Status^Digital input 1;
                               .nRawEncoderULINT      := TIIB[EL5042_SL1K2_PITCH_VERT]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:MMS:PITCH
    '}
    M19 : ST_MotionStage := (sName:= 'SL1K2:EXIT:MMS:PITCH',fVelocity :=0.12);// Air Pitch

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_SL1K2_VERT]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_SL1K2_VERT]^STM Status^Status^Digital input 2;
                               .nRawEncoderULINT      := TIIB[EL5042_SL1K2_PITCH_VERT]^FB Inputs Channel 2^Position'}
    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:MMS:VERT
    '}
    M20 : ST_MotionStage := (sName:= 'SL1K2:EXIT:MMS:VERT',fVelocity :=0.3); // Air Vertical

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_SL1K2_ROLL]^STM Status^Status^Digital input 2;
                              .bLimitBackwardEnable:=TIIB[EL7047_SL1K2_ROLL]^STM Status^Status^Digital input 1;
                              .nRawEncoderULINT      := TIIB[EL5042_SL1K2_ROLL_GAP]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:MMS:ROLL
    '}
    M21 : ST_MotionStage := (sName:= 'SL1K2:EXIT:MMS:ROLL',fVelocity :=0.24); // Air Roll

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_SL1K2_GAP]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7047_SL1K2_GAP]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT      := TIIB[EL5042_SL1K2_ROLL_GAP]^FB Inputs Channel 2^Position'}
    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:MMS:GAP
    '}
    M22 : ST_MotionStage := (sName:= 'SL1K2:EXIT:MMS:GAP',fVelocity :=0.1); // GAP

    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7047_SL1K2_YAG]^STM Status^Status^Digital input 2;
                              .bLimitBackwardEnable:=TIIB[EL7047_SL1K2_YAG]^STM Status^Status^Digital input 1;
                              .nRawEncoderULINT      := TIIB[EL5042_SL1K2_YAG]^FB Inputs Channel 1^Position'}
    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:MMS:YAG
    '}
    M23 : ST_MotionStage := (sName:= 'SL1K2:EXIT:MMS:YAG',fVelocity :=0.5); // YAG


    // ST1K1-ZOS-MMS
    {attribute 'pytmc' := 'pv: ST1K1:ZOS:MMS'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable  := TIIB[ST1K1-EL7041]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable := TIIB[ST1K1-EL7041]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT     :=TIIB[ST1K1-EL5042]^FB Inputs Channel 1^Position;
                              .bBrakeRelease        := TIIB[ST1K1-EL2008]^Channel 1^Output'}
    M24: ST_MotionStage := (sName := 'ST1K1:ZOS:MMS');

    // MR2K2-FLAT axes
    {attribute 'pytmc' := 'pv: MR2K2:FLAT:MMS:X'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M2K2X]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M2K2X]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M2K2X_M2K2Y]^FB Inputs Channel 1^Position'}
    M25 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=FALSE); //X mot
       fbMotionStageM25 : FB_MotionStage;
    {attribute 'pytmc' := 'pv: MR2K2:FLAT:MMS:Y'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M2K2Y]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M2K2Y]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M2K2X_M2K2Y]^FB Inputs Channel 2^Position'}
    M26 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=TRUE); //Y mot
    fbMotionStageM26 : FB_MotionStage;
    {attribute 'pytmc' := 'pv: MR2K2:FLAT:MMS:PITCH'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M2K2rX]^STM Status^Status^Digital input 2;
                              .bLimitBackwardEnable:=TIIB[EL7041_M2K2rX]^STM Status^Status^Digital input 1;
                              .nRawEncoderULINT:=TIIB[EL5042_M2K2rX]^FB Inputs Channel 1^Position'}
    M27 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=TRUE); //Pitch mot
    fbMotionStageM27 : FB_MotionStage;

    // MR3K2-KBH axes
    {attribute 'pytmc' := 'pv: MR3K2:KBH:MMS:X'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M3K2X]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M3K2X]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M3K2X_M3K2Y]^FB Inputs Channel 1^Position'}
    M28 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=TRUE); //X mot
    fbMotionStageM28 : FB_MotionStage;
    {attribute 'pytmc' := 'pv: MR3K2:KBH:MMS:Y'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M3K2Y]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M3K2Y]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M3K2X_M3K2Y]^FB Inputs Channel 2^Position'}
    M29 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=FALSE); //Y mot
    fbMotionStageM29 : FB_MotionStage;
    {attribute 'pytmc' := 'pv: MR3K2:KBH:MMS:PITCH'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M3K2rY]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M3K2ry]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M3K2rY]^FB Inputs Channel 1^Position'}
    M30 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=TRUE); //Pitch mot
    fbMotionStageM30 : FB_MotionStage;
    {attribute 'pytmc' := ' pv: MR3K2:KBH:MMS:BEND:US'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable :=TIIB[EL7041_M3K2_BEND_US]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M3K2_BEND_US]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:= TIIB[EL5042_M3K2_BEND_USDS]^FB Inputs Channel 1^Position'}
    M31 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, bPowerSelf:=TRUE); //MR3K2 US BEND
    fbMotionStageM31 : FB_MotionStage;
    {attribute 'pytmc' := 'pv: MR3K2:KBH:MMS:BEND:DS'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable :=TIIB[EL7041_M3K2_BEND_DS]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M3K2_BEND_DS]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:= TIIB[EL5042_M3K2_BEND_USDS]^FB Inputs Channel 2^Position'}
    M32 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, bPowerSelf:=TRUE); //MR3K2 DS BEND
    fbMotionStageM32 : FB_MotionStage;


    // MR4K2-KBV axes
    {attribute 'pytmc' := 'pv: MR4K2:KBV:MMS:X'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M4K2X]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M4K2X]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M4K2X_M4K2Y]^FB Inputs Channel 1^Position'}
    M33 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=FALSE); //X mot
    fbMotionStageM33 : FB_MotionStage;
    {attribute 'pytmc' := 'pv: MR4K2:KBV:MMS:Y'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M4K2Y]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M4K2Y]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M4K2X_M4K2Y]^FB Inputs Channel 2^Position'}
    M34 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=TRUE); //Y mot
    fbMotionStageM34 : FB_MotionStage;
    {attribute 'pytmc' := 'pv: MR4K2:KBV:MMS:PITCH'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable:=TIIB[EL7041_M4K2rX]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M4K2rX]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:=TIIB[EL5042_M4K2rX]^FB Inputs Channel 1^Position'}
    M35 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.ALWAYS, bPowerSelf:=TRUE); //Pitch mot
    fbMotionStageM35 : FB_MotionStage;
    {attribute 'pytmc' := ' pv: MR4K2:KBV:MMS:BEND:US'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable :=TIIB[EL7041_M4K2_BEND_US]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M4K2_BEND_US]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:= TIIB[EL5042_M4K2_BEND_USDS]^FB Inputs Channel 1^Position'}
    M36 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, bPowerSelf:=TRUE); //MR4K2 US BEND
    fbMotionStageM36 : FB_MotionStage;
    {attribute 'pytmc' := 'pv: MR4K2:KBV:MMS:BEND:DS'}
    {attribute 'TcLinkTo' := '.bLimitForwardEnable :=TIIB[EL7041_M4K2_BEND_DS]^STM Status^Status^Digital input 1;
                              .bLimitBackwardEnable:=TIIB[EL7041_M4K2_BEND_DS]^STM Status^Status^Digital input 2;
                              .nRawEncoderULINT:= TIIB[EL5042_M4K2_BEND_USDS]^FB Inputs Channel 2^Position'}
    M37 : ST_MotionStage := (nEnableMode:=ENUM_StageEnableMode.DURING_MOTION, bPowerSelf:=TRUE); //MR4K2 DS BEND
    fbMotionStageM37 : FB_MotionStage;


    sio_current AT%I* : UINT;

    sio_load AT%I* : UINT;
    dummyBool: BOOL;
END_VAR

POUs

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_AddChannelToHeader

FUNCTION F_AddChannelToHeader : BOOL
// Adds data about our channel to the header
VAR_IN_OUT
    stHeader: DUT_Header_NW;
    stChannel: DUT_Channel_NW;
END_VAR
IF stChannel.channel = 0 THEN
    F_AddChannelToHeader := FALSE;
ELSE
    stHeader.channelMask := stHeader.channelMask + SHL(1, stChannel.channel - 1);
    IF stChannel.error > 0 THEN
        stHeader.errorMask := stHeader.errorMask + SHL(1, stChannel.channel - 1);
    END_IF
    F_AddChannelToHeader := TRUE;
END_IF

END_FUNCTION
Related:

FB_AssembleChannel

FUNCTION_BLOCK FB_AssembleChannel
VAR_INPUT
    // Raw count from the encoder, taken from ST_MotionStage.nEncoderCount
    nEncCount : UDINT;
    // Latched rising edge timestamp from EL1252-0050 terminal.
    nTiming : ULINT;
    // Scale as reported in ST_MotionStage.stAxisParameters.fEncScaleFactorNumerator
    fScale : LREAL;
    // Serial number of the encoder
    sHardwareID : STRING(15);
    // Channel number for the data packet
    nChannel : USINT;
    // Error code for the data packet
    nError : USINT;
    // Selector for acquisition mode (currently unused)
    nMode : USINT;
    // Scale denominator as reported in ST_MotionStage.stAxisParameters.fEncScaleFactorDenominator
    fScaleDenominator: LREAL;
END_VAR
VAR_OUTPUT
    // Network-ready encoder channel structure
    stChannel : DUT_Channel_NW;
END_VAR
VAR
    nStringIter : USINT;
END_VAR
stChannel.count := HOST_TO_BE32(UDINT_TO_DWORD(nEncCount));
stChannel.timing := HOST_TO_BE32(ULINT_TO_DWORD(nTiming)); //  Truncate to remove the "seconds" bytes.
// Scale may be numerator-only or numerator and denominator
IF fScaleDenominator = 1 AND fScale < 0 THEN
    // Old format
    stChannel.scale := HOST_TO_BE16(LREAL_TO_WORD(fScale * 1_000_000)); // Send as "nanometers per count" uint (assuming mm scale)
    stChannel.scale_denominator := 0;
ELSE
    // New format
    stChannel.scale := HOST_TO_BE16(LREAL_TO_WORD(fScale));
    stChannel.scale_denominator := HOST_TO_BE16(LREAL_TO_WORD(fScaleDenominator));
END_IF

FOR nStringIter := 0 TO 15 DO
    stChannel.hardwareID[nStringIter] := sHardwareID[nStringIter];
END_FOR

stChannel.channel := nChannel;
stChannel.error := nError;
stChannel.mode := nMode;

END_FUNCTION_BLOCK
Related:

FB_AssembleHeader

FUNCTION_BLOCK FB_AssembleHeader
VAR_INPUT
    // The count of how many frames we've sent, e.g. 1 on the first send, 2 on the second...
    nFrameCount : UINT;
    // Protocol version. This is set for you by this function block.
    nMajor : UINT := 2;
    nMinor : USINT := 0;
    nMicro : USINT := 0;
    // Unambiguous identifier for the PLC
    sHardwareID : STRING(15);
    // Selector for acquisition mode (currently unused)
    nMode : USINT;
END_VAR
VAR_OUTPUT
    // Almost ready encoder header structure. See FB_AddChannelToHeader.
    stHeader : DUT_Header_NW;
END_VAR
VAR
    nStringIter : USINT;
END_VAR
stHeader.frameCount := HOST_TO_BE16(UINT_TO_WORD(nFrameCount));
stHeader.major := HOST_TO_BE16(UINT_TO_WORD(nMajor));
stHeader.minor := USINT_TO_BYTE(nMinor);
stHeader.micro := USINT_TO_BYTE(nMicro);

FOR nStringIter := 0 TO 15 DO
    stHeader.hardwareID[nStringIter] := sHardwareID[nStringIter];
END_FOR

stHeader.mode := nMode;

END_FUNCTION_BLOCK
Related:

FB_BufferedSocketSend

FUNCTION_BLOCK FB_BufferedSocketSend
VAR_INPUT
    stData: DUT_01_Channel_NW;
    bNewData: BOOL;
    hSocket: T_HSOCKET;
    sRemoteHost: STRING;
    nRemotePort: UDINT;
END_VAR
VAR_OUTPUT
END_VAR
VAR CONSTANT
    BUFFER_SIZE: UINT := 16;
END_VAR
VAR
    // Input handling
    rtNewData: R_TRIG;

    // Data buffer and iteration
    arrBuffer: ARRAY[0..BUFFER_SIZE-1] OF DUT_01_Channel_NW;
    nDataRecvCount: ULINT;
    nDataSentCount: ULINT;
    nIndex: UINT;

    // Actually send the data
    fbSocketSend: FB_SocketUdpSendTo;
    bExecuteSend: BOOL;
END_VAR
rtNewData(CLK:=bNewData);
IF rtNewData.Q THEN
    arrBuffer[nDataRecvCount MOD BUFFER_SIZE] := stData;
    nDataRecvCount := nDataRecvCount + 1;
END_IF
IF nDataRecvCount > nDataSentCount AND NOT fbSocketSend.bBusy THEN
    nIndex := ULINT_TO_UINT(nDataSentCount MOD BUFFER_SIZE);
    bExecuteSend := TRUE;
    nDataSentCount := nDataSentCount + 1;
END_IF
fbSocketSend(
    hSocket:=hSocket,
    sRemoteHost:=sRemoteHost,
    nRemotePort:=nRemotePort,
    cbLen:=SIZEOF(arrBuffer[nIndex]),
    pSrc:=ADR(arrBuffer[nIndex]),
    bExecute:=bExecuteSend,
);
fbSocketSend(
    bExecute:=FALSE,
);
bExecuteSend := FALSE;

END_FUNCTION_BLOCK
Related:

FB_UDPSocketHandler

FUNCTION_BLOCK FB_UDPSocketHandler
VAR_INPUT
    bEnable : BOOL;
    bReset : BOOL;
    nLocalPort: UDINT;
    tRetry : TIME := T#1h;
    sLocalIP : T_IPv4Addr;
END_VAR
VAR_OUTPUT
    bReady : BOOL;
    hSocket : T_HSOCKET;
END_VAR
VAR
    rtReset : R_TRIG;
    tonRetryConnection : TON; // Retry after an hour

    fbGetAdapterIP : FB_GetAdaptersInfo := (bExecute := TRUE, sNetID := ''); // Acquire IP of the correct adapter
    fbSocket : FB_ConnectionlessSocket;

    ctuSocketError : CTU := (PV:=3);

    bSocketEnable : BOOL;
    bAdapterSet : BOOL;

    idxPortFind : UDINT;
END_VAR
IF sLocalIP <> '' THEN
    fbSocket.sLocalHost := sLocalIP;
    bSocketEnable := TRUE;
    bAdapterSet := TRUE;
END_IF
IF bEnable AND NOT bAdapterSet THEN
    fbGetAdapterIP();
    IF NOT (fbGetAdapterIP.bBusy or fbGetAdapterIP.bError) THEN
        FOR idxPortFind := 0 TO MAX_LOCAL_ADAPTERS DO
            IF FIND(fbGetAdapterIP.arrAdapters[idxPortFind].sIpAddr,
               GVL_Logger.sIpTidbit) <> 0 THEN
                fbSocket.sLocalHost := fbGetAdapterIP.arrAdapters[idxPortFind].sIpAddr;
                bSocketEnable := TRUE;
                bAdapterSet := TRUE;
                EXIT;
            END_IF
        END_FOR
    END_IF
END_IF

(* Ensure the socket is ready for when data is emitted *)
rtReset(CLK:=bReset);

IF (rtReset.Q AND fbSocket.bEnable) THEN
    fbSocket(bEnable:=FALSE);
END_IF

// Disable fbSocket if too many errors occur
ctuSocketError(CU:=fbSocket.bError, RESET:=tonRetryConnection.Q OR rtReset.Q);
bSocketEnable R= ctuSocketError.Q;

// Retry an hour later
tonRetryConnection(IN:=ctuSocketError.Q,
                   PT:=tRetry);
bSocketEnable S= tonRetryConnection.Q OR rtReset.Q;

fbSocket(
    nLocalPort:=nLocalPort,
    bEnable:=bSocketEnable AND bEnable,
    nMode:=CONNECT_MODE_ENABLEDBG,
);

bReady := (bAdapterSet AND fbSocket.bEnable AND NOT fbSocket.bError AND
           fbSocket.eState = E_SocketConnectionlessState.eSOCKET_CREATED);
hSocket := fbSocket.hSocket;

END_FUNCTION_BLOCK

FB_XS_YAG_States

FUNCTION_BLOCK FB_XS_YAG_States EXTENDS FB_PositionStateBase_WithPMPS
VAR_INPUT
        {attribute 'pytmc' := '
        pv: SET
        io: io
    '}
    enumSet: ENUM_XS_YAG_States;

    stOut: DUT_PositionState;
    stYag1: DUT_PositionState;
    stYag2: DUT_PositionState;

    bStatesLock: BOOL;
END_VAR
VAR_OUTPUT
     {attribute 'pytmc' := '
        pv: GET
        io: i
    '}
    enumGet: ENUM_XS_YAG_States;
END_VAR
VAR
     bXSInit: BOOL :=TRUE;
END_VAR
VAR CONSTANT
    fInDelta: LREAL := 2;
    fOutDelta: LREAL := 2;
    fInVelocity: LREAL := 0.5;
    fOutVelocity: LREAL := 0.5;
    fAccel: LREAL := 100;
    fOutDecel: LREAL := 25;
END_VAR
IF bXSInit THEN
    bXSInit := FALSE;

    stOut.sName := 'OUT';
    stOut.fVelocity := fOutVelocity;
    stOut.fDelta := fOutDelta;
    stOut.fAccel := fAccel;
    stOut.fDecel := fOutDecel;
    stOut.bMoveOk := TRUE;

    stYag1.sName := 'YAG1';
    stYag1.fVelocity := fInVelocity;
    stYag1.fDelta := fInDelta;
    stYag1.fAccel := fAccel;
    stYag1.fDecel := fAccel;
    stYag1.bMoveOk := TRUE;

    stYag2.sName := 'YAG2';
    stYag2.fVelocity := fInVelocity;
    stYag2.fDelta := fInDelta;
    stYag2.fAccel := fAccel;
    stYag2.fDecel := fAccel;
    stYag2.bMoveOk := TRUE;

    arrStates[1] := stOut;
    arrStates[2] := stYag1;
    arrStates[3] := stYag2;
END_IF

setState := enumSet;
Exec();
enumGet := getState;
enumSet := setState;

END_FUNCTION_BLOCK
Related:

P_Serial_Com

PROGRAM P_Serial_Com
VAR
    fbSerialLineControl_EL6001_M1K2: SerialLineControl;
END_VAR
//These are the global IOs...don't forget to copy your data into them

(* EL6001 Serial port 0 com function *)
fbSerialLineControl_EL6001_M1K2(Mode:= SERIALLINEMODE_EL6_22B (*SERIALLINEMODE_KL6_22B_STANDARD*),
                                pComIn:= ADR(Serial_stComIn_M1K2),
                                   pComOut:=ADR(Serial_stComOut_M1K2),
                                SizeComIn:= SIZEOF(Serial_stComIn_M1K2),
                                TxBuffer:= Serial_TXBuffer_M1K2,
                                RxBuffer:= Serial_RXBuffer_M1K2,
                                Error=> ,
                                ErrorID=> );

END_PROGRAM

PiezoSerial

PROGRAM PiezoSerial
VAR
    //PI Serial
    fbE621SerialDriver_M1K2 : FB_PI_E621_SerialDriver;
    rtInitParams_M1K2       :       R_TRIG;
    tonTimeoutRst_M1K2      : TON := (PT:=T#2S); //For timeout reset
END_VAR
//Piezo E-621
///////////////////////
fbE621SerialDriver_M1K2.i_xExecute := TRUE;
fbE621SerialDriver_M1K2.i_xExecute R= fbE621SerialDriver_M1K2.q_xDone;
fbE621SerialDriver_M1K2(iq_stPiezoAxis:= GVL_M1K2.M1K2_Pitch.Piezo,
                        iq_stSerialRXBuffer:= Serial_RXBuffer_M1K2,
                        iq_stSerialTXBuffer:= Serial_TXBuffer_M1K2);

END_PROGRAM
Related:

PRG_1_PlcTask

PROGRAM PRG_1_PlcTask
VAR
fbLogHandler: FB_LogHandler;
END_VAR
PRG_MR1K1_BEND();
PRG_MR1K1_BEND_BENDER();
PRG_MR1K2_SWITCH();
PRG_SP1K1_MONO();
PRG_SL1K2_EXIT();
PRG_ST1K1_ZOS();
//PRG_SPO_PMPS(); As per Jira Ticket LCLSECSD-249
PRG_2_PMPS_POST();
fbLogHandler();
PRG_ZeroOrder_PMPS();
PRG_MR2K2_FLAT();
PRG_MR3K2_KBH();
PRG_MR4K2_KBV();

END_PROGRAM
Related:

PRG_2_PMPS_POST

PROGRAM PRG_2_PMPS_POST
VAR
    fbArbiterIO: FB_SubSysToArbiter_IO;
    fb_vetoArbiter: FB_VetoArbiter;
    ff2_ff1_link_optics: FB_FastFault := (i_xAutoReset := TRUE, i_DevName := 'FF2 to FF1 Link Optics', i_Desc := 'This is virtual FF2 fault, Please see faulting optics devices', i_TypeCode := 16#FFFF);
    //Extra FFOs
    ffRIX01 : FB_FastFault := (
        i_xAutoReset := FALSE,
        i_DevName := 'RIX Tran 0.1',
        i_Desc := 'Faults when M1K1 is IN and transmission is above 0.1',
        i_TypeCode := 16#F509);
    ffRIX02 : FB_FastFault := (
        i_xAutoReset := FALSE,
        i_DevName := 'RIX Tran 0.2',
        i_Desc := 'Faults when M1K1 is IN and transmission is above 0.2',
        i_TypeCode := 16#F510);
    ffRIX05 : FB_FastFault := (
        i_xAutoReset := FALSE,
        i_DevName := 'RIX Tran 0.5',
        i_Desc := 'Faults when M1K1 is IN and transmission is above 0.5',
        i_TypeCode := 16#F511);
    delta: real:=0.1;
END_VAR
MOTION_GVL.fbStandardPMPSDB(
    io_fbFFHWO:= GVL_PMPS.fbFastFaultOutput1,
    bEnable:= TRUE,
    sPLCName:='plc-rixs-optics'
);
GVL_PMPS.fbFastFaultOutput1.Execute(i_xVeto := (PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.MR1K1_OUT])
                                             AND (NOT PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.MR1K1_IN]));
GVL_PMPS.fbFastFaultOutput2.Execute(i_xVeto := (PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.MR1K1_OUT]
                                        AND NOT PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.MR1K1_IN])
                                         OR PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.ST1K2]);

fbArbiterIO(
    Arbiter:=GVL_PMPS.fbArbiter1,
    fbFFHWO:=GVL_PMPS.fbFastFaultOutput1,
    i_bVeto := PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.MR1K1_OUT]
               AND NOT PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.MR1K1_IN]);

fb_vetoArbiter(bVeto:= PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.ST1K2],
                HigherAuthority := GVL_PMPS.fbArbiter1,
                LowerAuthority := GVL_PMPS.fbArbiter2,
                FFO := GVL_PMPS.fbFastFaultOutput2);

ff2_ff1_link_optics(
    io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1,
    i_xOK :=  GVL_PMPS.fbFastFaultOutput2.q_xFastFaultOut);

//Code for Removing arbitration
GVL_PMPS.fbArbiter1.AddRequest(nReqID := 47, stReqBp := PMPS_GVL.cstFullBeam, sDevName := 'rixs-optics');
GVL_PMPS.fbArbiter2.AddRequest(nReqID := 48, stReqBp := PMPS_GVL.cstFullBeam, sDevName := 'rixs-optics');

// Extra FFOs
ffRIX01.i_xOK := (PMPS_GVL.stCurrentBeamParameters.nTran <=0.1+(0.1*delta)); //0.1
ffRIX01(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
ffRIX02.i_xOK := (PMPS_GVL.stCurrentBeamParameters.nTran <=0.2+(0.2*delta));
ffRIX02(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
ffRIX05.i_xOK := (PMPS_GVL.stCurrentBeamParameters.nTran <=0.5+(0.5*delta));
ffRIX05(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);

GVL_PMPS.rPhotonEnergy := PMPS_GVL.stCurrentBeamParameters.neV;

END_PROGRAM
Related:

PRG_DAQ_ENCODER

PROGRAM PRG_DAQ_ENCODER
VAR
    // Inputs
    {attribute 'TcLinkTo' := 'TIIB[EVR]^Latch^LatchPos1'}
    iLatchPos AT %I*: ULINT;
    {attribute 'TcLinkTo' := 'TIIB[EVR]^Latch^LatchNeg1'}
    iLatchNeg AT %I*: ULINT;
    {attribute 'TcLinkTo' := 'TIIB[g_pi_up_dwn_e]^FB Inputs Channel 1^Position'}
    nEncoderCount AT %I*: ULINT;
    bSendToDAQ: BOOL := TRUE;
    bSendToTest: BOOL := FALSE;
    // rix-daq is the default test host
    sTestHost: STRING := '172.21.140.21';
    bUseHWTriggers: BOOL := TRUE;
    bUseSWTriggers: BOOL := FALSE;
    tSWTriggerDelay: TIME := T#8ms;

    // Outputs
    iTimeSincePos: ULINT;
    iMaxTime: ULINT;
    iMinTime: ULINT := 10000000000;
    fTimeInS: LREAL;
    iTriggerWidth: ULINT;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:DAQ:TRIG_RATE
        io: i
    '}
    fTriggerRate: LREAL;

    // Internals
    bNewTrigger: BOOL;
    tonSWTrigger: TON;
    iPrevLatchPos: ULINT;
    fMaxTimeInS: LREAL;
    fMinTimeInS: LREAL := 10000000;
    iTimeSinceLast: ULINT;
    nUpdateCycles: ULINT;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:DAQ:FRAME_COUNT
        io: i
    '}
    nFrameCount: UINT;
    stTaskInfo: PlcTaskSystemInfo;
    iUnderflowCount: ULINT;
    fUnderflowPercent: LREAL;
    fEncScale: LREAL;
    fEncScaleDenominator: LREAL;

    fbSocketHandler : FB_UDPSocketHandler;
    fbSocketSend : FB_BufferedSocketSend;
    fbSocketHandlerTest : FB_UDPSocketHandler;
    fbSocketSendTest : FB_BufferedSocketSend;
    payload : DUT_01_Channel_NW;
    fbHeader : FB_AssembleHeader := (sHardwareID := 'plc-tst-proto6');
    fbChannel : FB_AssembleChannel := (nChannel := 1);

    // Function blocks
    fbGetTaskIndex: GETCURTASKINDEX;

    // Temp testing
    nBusyCycles: UINT;
    nMaxBusyCycles: UINT;
    nDroppedFrames: UINT;

    // EPICS Diagnostics (reduce data size)
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:DAQ:ENC_COUNT
        io: i
    '}
    fEpicsEncCount: DINT;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:DAQ:TRIG_WIDTH
        io: i
    '}
    fEpicsTrigWidth: DINT;
END_VAR
IF fEncScale = 0 THEN
    fEncScale := M7.stAxisParameters.fEncScaleFactorNumerator;
    fEncScaleDenominator := M7.stAxisParameters.fEncScaleFactorDenominator;
END_IF

fbGetTaskIndex();
stTaskInfo := _TaskInfo[fbGetTaskIndex.index];
IF bUseHWTriggers THEN
    bNewTrigger := iLatchPos <> iPrevLatchPos;
ELSIF bUseSWTriggers THEN
    tonSWTrigger(
        IN:=NOT tonSWTrigger.Q,
        PT:=tSWTriggerDelay,
        Q=>bNewTrigger,
    );
ELSE
    bNewTrigger := FALSE;
END_IF

IF bNewTrigger THEN
    // Core timing check
    nUpdateCycles := nUpdateCycles + 1;
    iTimeSincePos := LINT_TO_ULINT(stTaskInfo.DcTaskTime) - iLatchPos - (stTaskInfo.CycleTime * 100) + (stTaskInfo.LastExecTime * 100);
    IF iTimeSincePos > 9223372036854775800 THEN
        iTimeSincePos := 0;
        iUnderflowCount := iUnderflowCount + 1;
        IF nUpdateCycles > 0 THEN
            fUnderFlowPercent := 100 * ULINT_TO_LREAL(iUnderflowCount) / ULINT_TO_LREAL(nUpdateCycles);
        END_IF
    END_IF

    // Stats
    iMaxTime := MAX(iTimeSincePos, iMaxTime);
    iMinTime := MIN(iTimeSincePos, iMinTime);
    fTimeInS := ULINT_TO_LREAL(iTimeSincePos) / 1_000_000_000;
    fMinTimeInS := MIN(fTimeInS, fMinTimeInS);
    fMaxTimeInS := MAX(fTimeInS, fMaxTimeInS);
    iTimeSinceLast := iLatchPos - iPrevLatchPos;
    IF iTimeSinceLast > 0 THEN
        fTriggerRate := 1_000_000_000/ULINT_TO_LREAL(iTimeSinceLast);
    END_IF
    IF iLatchNeg > iLatchPos THEN
        iTriggerWidth := iLatchNeg - iLatchPos;
    END_IF

    // Assemble Packet
    nFrameCount := ULINT_TO_UINT(nUpdateCycles);
    fbHeader(
        nFrameCount := nFrameCount,
        sHardwareID := 'plc-rixs-optics',
        stHeader => payload.header
    );
    fbChannel(
        nEncCount := ULINT_TO_UDINT(nEncoderCount),
        nTiming := ULINT_TO_UDINT(iTimeSincePos),
        fScale := fEncScale,
        nError := UDINT_TO_USINT(M7.nErrorId),
        sHardwareID := 'SP1K1:G_PITCH',
        fScaleDenominator := fEncScaleDenominator,
        stChannel => payload.channel1
    );
    F_AddChannelToHeader(
        stHeader := payload.header,
        stChannel := payload.channel1,
    );

    // Make sure we don't send packets on the same trigger twice
    iPrevLatchPos := iLatchPos;
END_IF

// Create our sockets
// For the private DAQ network
fbSocketHandler(
    bEnable := TRUE,
    tRetry := T#30s,
    nLocalPort := 0,
    sLocalIP := '192.168.0.3'
);
// For the CDS network (testing)
fbSocketHandlerTest(
    bEnable := TRUE,
    tRetry := T#30s,
    nLocalPort := 0,
    sLocalIP := '172.21.140.71'
);

// Sent to the DAQ
fbSocketSend(
    stData := payload,
    bNewData := bSendToDAQ AND bNewTrigger,
    hSocket := fbSocketHandler.hSocket,
    sRemoteHost := '192.168.0.8',
    nRemotePort := 5006,
);

// Sent to test host (default: rix-daq)
fbSocketSendTest(
    stData := payload,
    bNewData := bSendToTest AND bNewTrigger,
    hSocket := fbSocketHandlerTest.hSocket,
    sRemoteHost := sTestHost,
    nRemotePort := 5006,
);

// Update EPICS-only vars
fEpicsEncCount := ULINT_TO_DINT(nEncoderCount);
fEpicsTrigWidth := ULINT_TO_DINT(iTriggerWidth);

END_PROGRAM
Related:

PRG_MR1K1_BEND

PROGRAM PRG_MR1K1_BEND
VAR
    {attribute 'TcLinkTo' := '.fbRunHOMS.bSTOEnable1:=TIIB[EL1004_M1K1_STO]^Channel 1^Input;
                              .fbRunHOMS.bSTOEnable2:=TIIB[EL1004_M1K1_STO]^Channel 2^Input;
                              .fbRunHOMS.stYupEnc.Count:=TIIB[EL5042_M1K1_Yupdwn]^FB Inputs Channel 1^Position;
                              .fbRunHOMS.stYdwnEnc.Count:=TIIB[EL5042_M1K1_Yupdwn]^FB Inputs Channel 2^Position;
                              .fbRunHOMS.stXupEnc.Count:=TIIB[EL5042_M1K1_Xupdwn]^FB Inputs Channel 1^Position;
                              .fbRunHOMS.stXdwnEnc.Count:=TIIB[EL5042_M1K1_Xupdwn]^FB Inputs Channel 2^Position'}
    {attribute 'pytmc' := '
        pv: MR1K1:BEND
    '}
    M1K1 : DUT_HOMS;

    // Encoder Arrays/RMS Watch:
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:Y
    '}
    fbYRMSErrorM1K1 : FB_RMSWatch;
    fMaxYRMSErrorM1K1 : LREAL;
    fMinYRMSErrorM1K1 : LREAL;

    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:X
    '}
    fbXRMSErrorM1K1 : FB_RMSWatch;
    fMaxXRMSErrorM1K1 : LREAL;
    fMinXRMSErrorM1K1 : LREAL;

    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:PITCH
    '}
    fbPitchRMSErrorM1K1 : FB_RMSWatch;
    fMaxPitchRMSErrorM1K1 : LREAL;
    fMinPitchRMSErrorM1K1 : LREAL;

    // Pitch Control
    fbM1K1PitchControl : FB_PitchControl;
    bM1K1PitchDone : BOOL;
    bM1K1PitchBusy : BOOL;

    // 3-15-20 Having issues with pitch control on new Axilon systems (M1K2)
    // Should test on this one to see if common to all new systems
    // Using stepper only for now
    fbMotionStage_m16 : FB_MotionStage;

    // Raw Encoder Counts
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:YUP:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntYupM1K1 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:YDWN:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntYdwnM1K1 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:XUP:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntXupM1K1 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:XDWN:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntXdwnM1K1 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:PITCH:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntPitchM1K1 : UDINT;

    // Encoder Reference Values
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:YUP:REF
        field: EGU cnt
        io: i
    '}
    nEncRefYupM1K1 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:YDWN:REF
        field: EGU cnt
        io: i
    '}
    nEncRefYdwnM1K1 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:XUP:REF
        field: EGU cnt
        io: i
    '}
    nEncRefXupM1K1 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:XDWN:REF
        field: EGU cnt
        io: i
    '}
    nEncRefXdwnM1K1 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:PITCH:REF
        field: EGU cnt
        io: i
    '}
    nEncRefPitchM1K1 : UDINT;
    mcReadParameterPitchM1K1 : MC_ReadParameter;
    fEncRefPitchM1K1_urad : LREAL; // Current Pitch encoder offset in urad

    // Common
    fEncLeverArm_mm : LREAL := 410.0;

    // Mirror LEDs
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:LED:01:PWR
        io: io
        field: ZNAM OFF
        field: ONAM ON
    '}
    {attribute 'TcLinkTo' := 'TIIB[EL2024-0010_M1K1_BEND_LED1]^Channel 1^Output'}
    bLEDPower01 AT %Q*: BOOL;

    {attribute 'pytmc' := '
        pv: MR1K1:BEND:LED:02:PWR
        io: io
        field: ZNAM OFF
        field: ONAM ON
    '}
    {attribute 'TcLinkTo' := 'TIIB[EL2024-0010_M1K1_BEND_LED1]^Channel 2^Output'}
    bLEDPower02 AT %Q*: BOOL;

    {attribute 'pytmc' := '
        pv: MR1K1:BEND:LED:03:PWR
        io: io
        field: ZNAM OFF
        field: ONAM ON
    '}
    {attribute 'TcLinkTo' := 'TIIB[EL2024-0010_M1K1_BEND_LED1]^Channel 3^Output'}
    bLEDPower03 AT %Q*: BOOL;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:LED:04:PWR
        io: io
        field: ZNAM OFF
        field: ONAM ON
    '}
    {attribute 'TcLinkTo' := 'TIIB[EL2024-0010_M1K1_BEND_LED1]^Channel 4^Output'}
    bLEDPower04 AT %Q*: BOOL;

    // MR1K1 Y Encoder for PMPS Veto Device
    ///////////////////////////////////////
        {attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^IO Outputs^MR1K1_Y_ENC'}
        nMR1K1_Y_ENC_PMPS AT %Q* : UDINT;
        {attribute 'TcLinkTo' := 'TIIB[EL5042_M1K1_Yupdwn]^FB Inputs Channel 1^Status^Ready'}
        bMR1K1_Y_ENC_Ready AT %I* : BOOL;
        {attribute 'TcLinkTo' := 'TIIB[EL5042_M1K1_Yupdwn]^FB Inputs Channel 1^Status^TxPDO State'}
        bMR1K1_Y_ENC_TxPDO AT %I* : BOOL;

    // M1K1 Flow Press Sensors
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_M1K1_FWM_PRSM]^AI Standard Channel 1^Value'}
    fM1K1_Flow_1 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:FWM:1
        field: EGU lpm
        io: i
    '}
    fM1K1_Flow_1_val : LREAL;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_M1K1_FWM_PRSM]^AI Standard Channel 2^Value'}
    fM1K1_Flow_2 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:FWM:2
        field: EGU lpm
        io: i
    '}
    fM1K1_Flow_2_val : LREAL;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_M1K1_FWM_PRSM]^AI Standard Channel 3^Value'}
    fM1K1_Press_1 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:PRSM:1
        field: EGU bar
        io: i
    '}
    fM1K1_Press_1_val : LREAL;

    {attribute 'pytmc' := 'pv: MR1K1:BEND:COATING'}
    fbCoatingStates: FB_PositionStatePMPS1D;
    {attribute 'pytmc' := '
      pv: MR1K1:BEND:COATING:STATE:SET
      io: io
    '}
    eStateSet: E_MR1K1_States;
    {attribute 'pytmc' := '
      pv: MR1K1:BEND:COATING:STATE:GET
      io: i
    '}
    eStateGet: E_MR1K1_States;
    fbYSetup: FB_StateSetupHelper;
    //fbXSetup: FB_StateSetupHelper;

    astCoatingStatesY: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
    //astCoatingStatesX: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;

END_VAR
// M1K1
M1K1.fbRunHOMS(stYup:=M12,
               stYdwn:=M13,
               stXup:=M14,
               stXdwn:=M15,
               stPitch:=M16,
               nYupEncRef:=GVL_M1K1_Constants.nYUP_ENC_REF,
               nYdwnEncRef:=GVL_M1K1_Constants.nYDWN_ENC_REF,
               nXupEncRef:=GVL_M1K1_Constants.nXUP_ENC_REF,
               nXdwnEncRef:=GVL_M1K1_Constants.nXDWN_ENC_REF,
               bExecuteCoupleY:=M1K1.bExecuteCoupleY,
               bExecuteCoupleX:=M1K1.bExecuteCoupleX,
               bExecuteDecoupleY:=M1K1.bExecuteDecoupleY,
               bExecuteDecoupleX:=M1K1.bExecuteDecoupleX,
               bGantryAlreadyCoupledY=>M1K1.bGantryAlreadyCoupledY,
               bGantryAlreadyCoupledX=>M1K1.bGantryAlreadyCoupledX,
               nCurrGantryY=>M1K1.nCurrGantryY,
               nCurrGantryX=>M1K1.nCurrGantryX);

// No slave motion through Epics
M13.bExecute := FALSE; // M1K1-Ydwn
M15.bExecute := FALSE; // M1K1-Xdwn

// Convert nCurrGantry to um (smaller number) to read out in epics
M1K1.fCurrGantryY_um := LINT_TO_REAL(M1K1.nCurrGantryY) / 1000.0;
M1K1.fCurrGantryX_um := LINT_TO_REAL(M1K1.nCurrGantryX) / 1000.0;

// FB_MotionStage's for non-piezo axes
fbMotionStage_m12(stMotionStage:=M12);
fbMotionStage_m13(stMotionStage:=M13);
fbMotionStage_m14(stMotionStage:=M14);
fbMotionStage_m15(stMotionStage:=M15);

// Calculate Pitch RMS Error:
fbYRMSErrorM1K1(stMotionStage:=M12,
                fMaxRMSError=>fMaxYRMSErrorM1K1,
                fMinRMSError=>fMinYRMSErrorM1K1);

fbXRMSErrorM1K1(stMotionStage:=M14,
                fMaxRMSError=>fMaxXRMSErrorM1K1,
                fMinRMSError=>fMinXRMSErrorM1K1);

fbPitchRMSErrorM1K1(stMotionStage:=M16,
                    fMaxRMSError=>fMaxPitchRMSErrorM1K1,
                    fMinRMSError=>fMinPitchRMSErrorM1K1);

(*
// Pitch Control
fbM1K1PitchControl(Pitch:=GVL_M1K1.M1K1_Pitch,
                   Stepper:=M16,
                   lrCurrentSetpoint:=M16.fPosition,
                   q_bDone=>bM1K1PitchDone,
                   q_bBusy=>bM1K1PitchBusy);
// When STO hit, need to reset SP
IF NOT M16.bHardwareEnable THEN
    M16.fPosition := M16.stAxisStatus.fActPosition;
END_IF
*)
// 3-15-20: Having issues with pitch control on new Axilon systems, should test here
fbMotionStage_m16(stMotionStage:=M16);

// Raw Encoder Counts For Epics
nEncCntYupM1K1 := ULINT_TO_UDINT(M1K1.fbRunHOMS.stYupEnc.Count);
nEncCntYdwnM1K1 := ULINT_TO_UDINT(M1K1.fbRunHOMS.stYdwnEnc.Count);
nEncCntXupM1K1 := ULINT_TO_UDINT(M1K1.fbRunHOMS.stXupEnc.Count);
nEncCntXdwnM1K1 := ULINT_TO_UDINT(M1K1.fbRunHOMS.stXdwnEnc.Count);
nEncCntPitchM1K1 := LINT_TO_UDINT(GVL_M1K1.M1K1_Pitch.diEncCnt);

// Encoder Reference Values For Epics
nEncRefYupM1K1 := ULINT_TO_UDINT(GVL_M1K1_Constants.nYUP_ENC_REF);
nEncRefYdwnM1K1 := ULINT_TO_UDINT(GVL_M1K1_Constants.nYDWN_ENC_REF);
nEncRefXupM1K1 := ULINT_TO_UDINT(GVL_M1K1_Constants.nXUP_ENC_REF);
nEncRefXdwnM1K1 := ULINT_TO_UDINT(GVL_M1K1_Constants.nXDWN_ENC_REF);
mcReadParameterPitchM1K1(Axis:=M16.Axis,
                         Enable:=TRUE,
                         ParameterNumber:=MC_AxisParameter.AxisEncoderOffset,
                         ReadMode:=READMODE_CYCLIC,
                         Value=>fEncRefPitchM1K1_urad);

nEncRefPitchM1K1 := LREAL_TO_UDINT(ABS(fEncRefPitchM1K1_urad) * fEncLeverArm_mm);

// Export the Y encoder value for PMPS veto device evaluation
    IF bMR1K1_Y_ENC_Ready AND NOT bMR1K1_Y_ENC_TxPDO THEN
        nMR1K1_Y_ENC_PMPS := nEncCntYupM1K1;
    ELSE
        nMR1K1_Y_ENC_PMPS := 0;
    END_IF

    // LAMP KB Flow Pressure Sensors
    fM1K1_Flow_1(iTermBits:=15, fTermMax:=5.0427, fTermMin:=0.050472);
    fM1K1_Flow_1_val := fM1K1_Flow_1.fReal;

    fM1K1_Flow_2(iTermBits:=15, fTermMax:=5.0427, fTermMin:=0.050472);
    fM1K1_Flow_2_val := fM1K1_Flow_2.fReal;

    fM1K1_Press_1(iTermBits:=15, fTermMax:=4.0, fTermMin:=0);
    fM1K1_Press_1_val := fM1K1_Press_1.fReal;

fbYSetup(stPositionState:=GVL_States.stDefaultOffsetY, bSetDefault:=TRUE);
//fbXSetup(stPositionState:=GVL_States.stDefaultOffsetX, bSetDefault:=TRUE);

fbYSetup(stPositionState:=astCoatingStatesY[E_MR1K1_States.B4C],
    sName:='B4C',
    sPmpsState:='MR1K1:BEND-B4C',
    nEncoderCount:=31911452
);
(*fbXSetup(stPositionState:=astCoatingStatesX[E_MR1K1_States.B4C],
    sName:='B4C',
    sPmpsState:='MR1K1:BEND-B4C',
    nEncoderCount:=19829647
);*)
fbYSetup(stPositionState:=astCoatingStatesY[E_MR1K1_States.OUT],
    sName:='OUT',
    sPmpsState:='MR1K1:BEND-OUT',
    nEncoderCount:=13412630,
    fDelta:=50
);
(*
// Out position determined solely by Y Axis
fbXSetup(stPositionState:=astCoatingStatesX[E_MR1K1_States.OUT],
    sName:='OUT',
    sPmpsState:='MR1K1:BEND-OUT',
    nEncoderCount:=ULINT_TO_UDINT(M14.nRawEncoderULINT)
);
*)
fbCoatingStates(
    stMotionStage:=Main.M12,
    astPositionState:=astCoatingStatesY,
    eEnumSet:=eStateSet,
    eEnumGet:=eStateGet,
    fbFFHWO:=GVL_PMPS.fbFastFaultOutput1,
    fbArbiter:=GVL_PMPS.fbArbiter1,
    bEnableMotion:=TRUE,
    bEnableBeamParams:=TRUE,
    sDeviceName:='MR1K1:BEND',
    sTransitionKey:='MR1K1:BEND-TRANSITION',
);

END_PROGRAM
Related:

PRG_MR1K1_BEND_BENDER

PROGRAM PRG_MR1K1_BEND_BENDER
VAR
    // Encoder Arrays/RMS Watch:
    //MR1K1 US BENDER ENC RMS
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:US
    '}
    fbBendUSRMSErrorM1K1 : FB_RMSWatch;
    fMaxBendUSRMSErrorM1K1 : LREAL;
    fMinBendUSRMSErrorM1K1 : LREAL;
    //MR1K1 DS BENDER ENC RMS
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:DS
    '}
    fbBendDSRMSErrorM1K1 : FB_RMSWatch;
    fMaxBendDSRMSErrorM1K1 : LREAL;
    fMinBendDSRMSErrorM1K1 : LREAL;

    // Encoder Reference Values
    //MR1K1 BEND US ENC REF
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:US:REF
        field: EGU cnt
        io: i
    '}
    nEncRefBendUSM1K1 : UDINT;
    //MR1K1 BEND DS ENC REF
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:DS:REF
        field: EGU cnt
        io: i
    '}
    nEncRefBendDSM1K1 : UDINT;

    // Encoder raw counts
    //M1K1 BEND US ENC CNT
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:US:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntBendUSM1K1 : UDINT;
    //M1K1 BEND DS ENC CNT
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:ENC:DS:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntBendDSM1K1 : UDINT;
    //Emergency Stop for MR1K1
    //Emergency Stop for MR1K1
    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^Term 5 (EK1122)^EK1100_MR1K1_BEND^EL1004_M1K1_BENDER_STO^Channel 1^Input'}
    M1K1BENDbSTOEnable1 AT %I* : BOOL;
    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^Term 5 (EK1122)^EK1100_MR1K1_BEND^EL1004_M1K1_BENDER_STO^Channel 2^Input'}
    M1K1BENDbSTOEnable2 AT %I* : BOOL;

    // MR1K1 Bender RTDs
    // MR1K1 US RTDs
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:RTD:US:1
        field: ASLO 0.01
        field: EGU C
        io: i
    '}
    fM1K1US_RTD_1 : REAL;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:RTD:US:2
        field: ASLO 0.01
        field: EGU C
        io: i
    '}
    fM1K1US_RTD_2 : REAL;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:RTD:US:3
        field: ASLO 0.01
        field: EGU C
        io: i
    '}
    fM1K1US_RTD_3 : REAL;

    // M1K1 DS RTDs
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:RTD:DS:1
        field: ASLO 0.01
        field: EGU C
        io: i
    '}
    fM1K1DS_RTD_1 : REAL;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:RTD:DS:2
        field: ASLO 0.01
        field: EGU C
        io: i
    '}
    fM1K1DS_RTD_2 : REAL;
    {attribute 'pytmc' := '
        pv: MR1K1:BEND:RTD:DS:3
        field: ASLO 0.01
        field: EGU C
        io: i
    '}
    fM1K1DS_RTD_3 : REAL;

    // RTD error bit
    bM1K1US_RTD_1_Err AT %I*: BOOL;
    bM1K1US_RTD_2_Err AT %I*: BOOL;
    bM1K1US_RTD_3_Err AT %I*: BOOL;
    bM1K1DS_RTD_1_Err AT %I*: BOOL;
    bM1K1DS_RTD_2_Err AT %I*: BOOL;
    bM1K1DS_RTD_3_Err AT %I*: BOOL;

    // Logging
    fbLogHandler : FB_LogHandler;
    fbBendUSRMSErrorMR1K1: INT;

    // PMPS
    ffBenderRange : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'MR1K1 Bender',
        i_Desc := 'Benders have moved out of range, monochromator beam may be focused where it can damage the BCS. Adjust bender to be within limits to clear fault',
        i_TypeCode := 16#402);
END_VAR
//FB_Motion stages for MR1K1 Benders
//MR1K1 BEND
fbMotionStage_m17(stMotionStage:=M17);
fbMotionStage_m18(stMotionStage:=M18);

// Calculate Pitch RMS Error for MR1K1 Benders:
//MR1K1 US BENDER ENC RMS
fbBendUSRMSErrorM1K1(stMotionStage:=M17,
                     fMaxRMSError=>fMaxBendUSRMSErrorM1K1,
                     fMinRMSError=>fMinBendUSRMSErrorM1K1);
//MR1K1 DS BENDER ENC RMS
fbBendDSRMSErrorM1K1(stMotionStage:=M18,
                     fMaxRMSError=>fMaxBendDSRMSErrorM1K1,
                     fMinRMSError=>fMinBendDSRMSErrorM1K1);


//STO for MR1K1 Benders
M17.bHardwareEnable := M1K1BENDbSTOEnable1 AND M1K1BENDbSTOEnable2;
M18.bHardwareEnable := M1K1BENDbSTOEnable1 AND M1K1BENDbSTOEnable2;

//Encoder reference positions for MR1K1
nEncRefBendUSM1K1  := ULINT_TO_UDINT(GVL_M1K1_BENDER_Constants.nM1K1bendUS_ENC_REF);
nEncRefBendDSM1K1  := ULINT_TO_UDINT(GVL_M1K1_BENDER_Constants.nM1K1bendDS_ENC_REF);

// M1K1 Bender Encoder Count Values For Epics
nEncCntBendUSM1K1  := ULINT_TO_UDINT(M17.nRawEncoderULINT);
nEncCntBendDSM1K1  := ULINT_TO_UDINT(M18.nRawEncoderULINT);

// MR1K1 Bender RTDs
fM1K1US_RTD_1 := INT_TO_REAL(GVL_M1K1_BENDER_RTD.nM1K1US_RTD_1);
fM1K1US_RTD_2 := INT_TO_REAL(GVL_M1K1_BENDER_RTD.nM1K1US_RTD_2);
fM1K1US_RTD_3 := INT_TO_REAL(GVL_M1K1_BENDER_RTD.nM1K1US_RTD_3);

fM1K1DS_RTD_1 := INT_TO_REAL(GVL_M1K1_BENDER_RTD.nM1K1DS_RTD_1);
fM1K1DS_RTD_2 := INT_TO_REAL(GVL_M1K1_BENDER_RTD.nM1K1DS_RTD_2);
fM1K1DS_RTD_3 := INT_TO_REAL(GVL_M1K1_BENDER_RTD.nM1K1DS_RTD_3);

// RTD not connected if T=0
bM1K1US_RTD_1_Err := fM1K1US_RTD_1 = 0;
bM1K1US_RTD_2_Err := fM1K1DS_RTD_2 = 0;
bM1K1US_RTD_3_Err := fM1K1US_RTD_3 = 0;
bM1K1DS_RTD_1_Err := fM1K1DS_RTD_1 = 0;
bM1K1DS_RTD_2_Err := fM1K1DS_RTD_2 = 0;
bM1K1DS_RTD_3_Err := fM1K1DS_RTD_3 = 0;


// M1K1 Bender RTD interlocks
M17.bHardwareEnable R= fM1K1US_RTD_1 > 10000 OR bM1K1US_RTD_1_Err;
M18.bHardwareEnable R= fM1K1DS_RTD_1 > 10000 OR bM1K1DS_RTD_1_Err;

ffBenderRange.i_xOK :=
    GVL_M1K1_BENDER_Constants.nM1K1bendUS_PMPS_LowerLimit < nEncCntBendUSM1K1 AND nEncCntBendUSM1K1 < GVL_M1K1_BENDER_Constants.nM1K1bendUS_PMPS_UpperLimit AND
    GVL_M1K1_BENDER_Constants.nM1K1bendDS_PMPS_LowerLimit < nEncCntBendDSM1K1 AND nEncCntBendDSM1K1 < GVL_M1K1_BENDER_Constants.nM1K1bendDS_PMPS_UpperLimit;
ffBenderRange(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);

M17.bLimitForwardEnable := TRUE;

END_PROGRAM
Related:

PRG_MR1K2_SWITCH

PROGRAM PRG_MR1K2_SWITCH
VAR
    {attribute 'TcLinkTo' := '.fbRunHOMS.bSTOEnable1:=TIIB[EL1004_M1K2_STO]^Channel 1^Input;
                              .fbRunHOMS.bSTOEnable2:=TIIB[EL1004_M1K2_STO]^Channel 2^Input;
                              .fbRunHOMS.stYupEnc.Count:=TIIB[EL5042_M1K2_Yleftright]^FB Inputs Channel 1^Position;
                              .fbRunHOMS.stYdwnEnc.Count:=TIIB[EL5042_M1K2_Yleftright]^FB Inputs Channel 2^Position;
                              .fbRunHOMS.stXupEnc.Count:=TIIB[EL5042_M1K2_Xupdwn]^FB Inputs Channel 1^Position;
                              .fbRunHOMS.stXdwnEnc.Count:=TIIB[EL5042_M1K2_Xupdwn]^FB Inputs Channel 2^Position'}
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH
    '}
    M1K2 : DUT_HOMS;

    // Encoder Arrays/RMS Watch:
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:Y
    '}
    fbYRMSErrorM1K2 : FB_RMSWatch;
    fMaxYRMSErrorM1K2 : LREAL;
    fMinYRMSErrorM1K2 : LREAL;

    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:X
    '}
    fbXRMSErrorM1K2 : FB_RMSWatch;
    fMaxXRMSErrorM1K2 : LREAL;
    fMinXRMSErrorM1K2 : LREAL;

    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:PITCH
    '}
    fbPitchRMSErrorM1K2 : FB_RMSWatch;
    fMaxPitchRMSErrorM1K2 : LREAL;
    fMinPitchRMSErrorM1K2 : LREAL;

    // Pitch Control
    fbM1K2PitchControl : FB_PitchControl;
    bM1K2PitchDone : BOOL;
    bM1K2PitchBusy : BOOL;

    // 3-15-20 Having issues with pitch control on new Axilon systems
    // Using stepper only for now
    fbMotionStage_m5 : FB_MotionStage;

    // Roll (Rotation about Z axis) induced from Ygantry:
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:ROLL
        field: EGU urad
        io: i
    '}
    fYRoll_urad : LREAL; // Roll about Z axis in urad

    // Raw Encoder Counts
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:YLEFT:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntYleftM1K2 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:YRIGHT:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntYrightM1K2 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:XUP:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntXupM1K2 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:XDWN:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntXdwnM1K2 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:PITCH:CNT
        field: EGU cnt
        io: i
    '}
    nEncCntPitchM1K2 : UDINT;

    // Encoder Reference Values
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:YLEFT:REF
        field: EGU cnt
        io: i
    '}
    nEncRefYleftM1K2 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:YRIGHT:REF
        field: EGU cnt
        io: i
    '}
    nEncRefYrightM1K2 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:XUP:REF
        field: EGU cnt
        io: i
    '}
    nEncRefXupM1K2 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:XDWN:REF
        field: EGU cnt
        io: i
    '}
    nEncRefXdwnM1K2 : UDINT;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:ENC:PITCH:REF
        field: EGU cnt
        io: i
    '}
    nEncRefPitchM1K2 : UDINT;
    mcReadParameterPitchM1K2 : MC_ReadParameter;
    fEncRefPitchM1K2_urad : LREAL; // Current Pitch encoder offset in urad

    // Common
    fEncLeverArm_mm : LREAL := 391.0;

// MR1K2 Flow Press Sensors
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_M1K2_FWM_PRSM]^AI Standard Channel 1^Value'}
    fM1K2_Flow_1 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:FWM:1
        field: EGU lpm
        io: i
    '}
    fM1K2_Flow_1_val : LREAL;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_M1K2_FWM_PRSM]^AI Standard Channel 2^Value'}
    fM1K2_Flow_2 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:FWM:2
        field: EGU lpm
        io: i
    '}
    fM1K2_Flow_2_val : LREAL;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_M1K2_FWM_PRSM]^AI Standard Channel 3^Value'}
    fM1K2_Press_1 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: MR1K2:SWITCH:PRSM:1
        field: EGU bar
        io: i
    '}
    fM1K2_Press_1_val : LREAL;

    {attribute 'pytmc' := 'pv: MR1K2:SWITCH:COATING'}
    fbCoatingStates: FB_PositionStatePMPS1D;
    {attribute 'pytmc' := '
      pv: MR1K2:SWITCH:COATING:STATE:SET
      io: io
    '}
    eStateSet: E_B4C_Rh_CoatingStates;
    {attribute 'pytmc' := '
      pv: MR1K2:SWITCH:COATING:STATE:GET
      io: i
    '}
    eStateGet: E_B4C_Rh_CoatingStates;
    fbYSetup: FB_StateSetupHelper;

    astCoatingStatesY: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;

END_VAR
// M1K2
M1K2.fbRunHOMS(stYup:=M1,
               stYdwn:=M2,
               stXup:=M3,
               stXdwn:=M4,
               stPitch:=M5,
               nYupEncRef:=GVL_M1K2_Constants.nYLEFT_ENC_REF,
               nYdwnEncRef:=GVL_M1K2_Constants.nYRIGHT_ENC_REF,
               nXupEncRef:=GVL_M1K2_Constants.nXUP_ENC_REF,
               nXdwnEncRef:=GVL_M1K2_Constants.nXDWN_ENC_REF,
               bExecuteCoupleY:=M1K2.bExecuteCoupleY,
               bExecuteCoupleX:=M1K2.bExecuteCoupleX,
               bExecuteDecoupleY:=M1K2.bExecuteDecoupleY,
               bExecuteDecoupleX:=M1K2.bExecuteDecoupleX,
               bGantryAlreadyCoupledY=>M1K2.bGantryAlreadyCoupledY,
               bGantryAlreadyCoupledX=>M1K2.bGantryAlreadyCoupledX,
               nCurrGantryY=>M1K2.nCurrGantryY,
               nCurrGantryX=>M1K2.nCurrGantryX);

// No slave motion through Epics
M2.bExecute := FALSE; // M1K2-Yright
M4.bExecute := FALSE; // M1K2-Xdwn

//Forcing limit switches for M1K2 pitch as the limit switches are no longer installed per ME they aren't able to install them precisely
M5.bLimitBackwardEnable := TRUE;
M5.bLimitForwardEnable := TRUE;

// Convert nCurrGantry to um (smaller number) to read out in epics
M1K2.fCurrGantryY_um := LINT_TO_REAL(M1K2.nCurrGantryY) / 1000.0;
M1K2.fCurrGantryX_um := LINT_TO_REAL(M1K2.nCurrGantryX) / 1000.0;

fYRoll_urad := (REAL_TO_LREAL(ATAN(M1K2.fCurrGantryY_um / GVL_M1K2_Constants.fRollLeverArm_um))) * EXPT(10, 6);

// FB_MotionStage's for non-piezo axes
fbMotionStage_m1(stMotionStage:=M1);
fbMotionStage_m2(stMotionStage:=M2);
fbMotionStage_m3(stMotionStage:=M3);
fbMotionStage_m4(stMotionStage:=M4);

// Calculate Pitch RMS Error:
fbYRMSErrorM1K2(stMotionStage:=M1,
                fMaxRMSError=>fMaxYRMSErrorM1K2,
                fMinRMSError=>fMinYRMSErrorM1K2);

fbXRMSErrorM1K2(stMotionStage:=M3,
                fMaxRMSError=>fMaxXRMSErrorM1K2,
                fMinRMSError=>fMinXRMSErrorM1K2);

fbPitchRMSErrorM1K2(stMotionStage:=M5,
                    fMaxRMSError=>fMaxPitchRMSErrorM1K2,
                    fMinRMSError=>fMinPitchRMSErrorM1K2);

(*
// Pitch Control
fbM1K2PitchControl(Pitch:=GVL_M1K2.M1K2_Pitch,
                   Stepper:=M5,
                   lrCurrentSetpoint:=M5.fPosition,
                   q_bDone=>bM1K2PitchDone,
                   q_bBusy=>bM1K2PitchBusy);
// When STO hit, need to reset SP
IF NOT M5.bHardwareEnable THEN
    M5.fPosition := M5.stAxisStatus.fActPosition;
END_IF
*)
// 3-15-20: Having issues with pitch control on new Axilon systems
fbMotionStage_m5(stMotionStage:=M5);

// Raw Encoder Counts For Epics
nEncCntYleftM1K2 := ULINT_TO_UDINT(M1K2.fbRunHOMS.stYupEnc.Count);
nEncCntYrightM1K2 := ULINT_TO_UDINT(M1K2.fbRunHOMS.stYdwnEnc.Count);
nEncCntXupM1K2 := ULINT_TO_UDINT(M1K2.fbRunHOMS.stXupEnc.Count);
nEncCntXdwnM1K2 := ULINT_TO_UDINT(M1K2.fbRunHOMS.stXdwnEnc.Count);
nEncCntPitchM1K2 := LINT_TO_UDINT(GVL_M1K2.M1K2_Pitch.diEncCnt);

// Encoder Reference Values For Epics
nEncRefYleftM1K2 := ULINT_TO_UDINT(GVL_M1K2_Constants.nYLEFT_ENC_REF);
nEncRefYrightM1K2 := ULINT_TO_UDINT(GVL_M1K2_Constants.nYRIGHT_ENC_REF);
nEncRefXupM1K2 := ULINT_TO_UDINT(GVL_M1K2_Constants.nXUP_ENC_REF);
nEncRefXdwnM1K2 := ULINT_TO_UDINT(GVL_M1K2_Constants.nXDWN_ENC_REF);
mcReadParameterPitchM1K2(Axis:=M5.Axis,
                         Enable:=TRUE,
                         ParameterNumber:=MC_AxisParameter.AxisEncoderOffset,
                         ReadMode:=READMODE_CYCLIC,
                         Value=>fEncRefPitchM1K2_urad);

nEncRefPitchM1K2 := LREAL_TO_UDINT(ABS(fEncRefPitchM1K2_urad) * fEncLeverArm_mm);

//FLOW AND PRESSURE SENSOR
// MR1K2 Flow Pressure Sensors
fM1K2_Flow_1(iTermBits:=15, fTermMax:=5.0427, fTermMin:=0.050472);
fM1K2_Flow_1_val := fM1K2_Flow_1.fReal;
fM1K2_Flow_2(iTermBits:=15, fTermMax:=5.0427, fTermMin:=0.050472);
fM1K2_Flow_2_val := fM1K2_Flow_2.fReal;
fM1K2_Press_1(iTermBits:=15, fTermMax:=4.0, fTermMin:=0);
fM1K2_Press_1_val := fM1K2_Press_1.fReal;

fbYSetup(stPositionState:=GVL_States.stDefaultOffsetY, bSetDefault:=TRUE);

fbYSetup(stPositionState:=astCoatingStatesY[E_B4C_Rh_CoatingStates.B4C],
    sName:='B4C',
    sPmpsState:='MR1K2:SWITCH-B4C',
    nEncoderCount:=91672358,
    fDelta:=5000
);
fbYSetup(stPositionState:=astCoatingStatesY[E_B4C_Rh_CoatingStates.Rh],
    sName:='Rh',
    sPmpsState:='MR1K2:SWITCH-RHODIUM',
    nEncoderCount:=106672361,
    fDelta:=5000
);
fbCoatingStates(
    stMotionStage:=Main.M1,
    astPositionState:=astCoatingStatesY,
    eEnumSet:=eStateSet,
    eEnumGet:=eStateGet,
    fbFFHWO:=GVL_PMPS.fbFastFaultOutput1,
    fbArbiter:=GVL_PMPS.fbArbiter1,
    bEnableMotion:=TRUE,
    bEnableBeamParams:=TRUE,
    sDeviceName:='MR1K2:SWITCH',
    sTransitionKey:='MR1K2:SWITCH-TRANSITION',
);

END_PROGRAM
Related:

PRG_MR2K2_FLAT

PROGRAM PRG_MR2K2_FLAT
VAR
    // Encoder Arrays/RMS Watch:
        //MR2K2 X ENC RMS
        {attribute 'pytmc' := '     pv: MR2K2:FLAT:ENC:X'}
        fbXRMSErrorM2K2 : FB_RMSWatch;
        fMaxXRMSErrorM2K2 : LREAL;
        fMinXRMSErrorM2K2 : LREAL;
        //MR2K2 Y ENC RMS
        {attribute 'pytmc' := '     pv: MR2K2:FLAT:ENC:Y'}
        fbYRMSErrorM2K2 : FB_RMSWatch;
        fMaxYRMSErrorM2K2 : LREAL;
        fMinYRMSErrorM2K2 : LREAL;
        //MR2K2 rX ENC RMS
        {attribute 'pytmc' := '     pv: MR2K2:FLAT:ENC:PITCH'}
        fbrXRMSErrorM2K2 : FB_RMSWatch;
        fMaxrXRMSErrorM2K2 : LREAL;
        fMinrXRMSErrorM2K2 : LREAL;

    // Encoder Reference Values
        //MR2K2 X ENC REF
        {attribute 'pytmc' := '
            pv: MR2K2:FLAT:ENC:X:REF
            field: EGU cnt
            io: i
        '}
        nEncRefXM2K2 : UDINT;
        //MR2K2 Y ENC REF
        {attribute 'pytmc' := '
            pv: MR2K2:FLAT:ENC:Y:REF
            field: EGU cnt
            io: i
        '}
        nEncRefYM2K2 : UDINT;
        //MR2K2 rX ENC REF
        {attribute 'pytmc' := '
            pv: MR2K2:FLAT:ENC:PITCH:REF
            field: EGU cnt
            io: i
        '}
        nEncRefrXM2K2 : UDINT;

    // Encoder raw counts
        //M2K2 FLAT X ENC CNT
        {attribute 'pytmc' := '
            pv: MR2K2:FLAT:ENC:X:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntXM2K2 : UDINT;
        //M2K2 FLAT Y ENC CNT
        {attribute 'pytmc' := '
            pv: MR2K2:FLAT:ENC:Y:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntYM2K2 : UDINT;
        //M2K2 FLAT rX ENC CNT
        {attribute 'pytmc' := '
            pv: MR2K2:FLAT:ENC:PITCH:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntrXM2K2 : UDINT;

    //Emergency Stop for MR2K2 //M2K2 needs an EL1004 term and an ESTOP added, not listed in I/O either

    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^Term 79 (EK1521-0010)^Term 306 (EK1501-0010)^Term 310 (EK1122)^EK1100_MR2K2^EL1004_M2K2_STO^Channel 1^Input'}
    M2K2FLATbSTOEnable1 AT %I* : BOOL;
    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^Term 79 (EK1521-0010)^Term 306 (EK1501-0010)^Term 310 (EK1122)^EK1100_MR2K2^EL1004_M2K2_STO^Channel 2^Input'}
    M2K2FLATbSTOEnable2 AT %I* : BOOL;
    {attribute 'TcLinkTo' := '.fbFlow_1.iRaw := TIIB[EL3054_MR2K2_FWM_PRSM]^AI Standard Channel 1^Value;
                                 .fbFlow_2.iRaw := TIIB[EL3054_MR2K2_FWM_PRSM]^AI Standard Channel 3^Value;
                              .fbPress_1.iRaw := TIIB[EL3054_MR2K2_FWM_PRSM]^AI Standard Channel 2^Value
    '}
    {attribute 'pytmc' := '
        pv: MR2K2:FLAT
    '}
    fbCoolingPanel : FB_Axilon_Cooling_2f1p;

    {attribute 'pytmc' := 'pv: MR2K2:FLAT:COATING'}
    fbCoatingStates: FB_PositionStatePMPS1D;
    {attribute 'pytmc' := '
      pv: MR2K2:FLAT:COATING:STATE:SET
      io: io
    '}
    eStateSet: E_B4C_Rh_CoatingStates;
    {attribute 'pytmc' := '
      pv: MR2K2:FLAT:COATING:STATE:GET
      io: i
    '}
    eStateGet: E_B4C_Rh_CoatingStates;
    fbXSetup: FB_StateSetupHelper;

    astCoatingStatesX: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;

END_VAR
//FB_Motion stages for MR2K2 Axes
//MR2K2 FLAT
fbMotionStageM25(stMotionStage:=M25);
fbMotionStageM26(stMotionStage:=M26);
fbMotionStageM27(stMotionStage:=M27);

// Calculate Pitch RMS Error for MR2K2 Axes:
//MR2K2 X ENC RMS
fbXRMSErrorM2K2(stMotionStage:=M25,
                     fMaxRMSError=>fMaxXRMSErrorM2K2,
                     fMinRMSError=>fMinXRMSErrorM2K2);
//MR2K2 X ENC RMS
fbYRMSErrorM2K2(stMotionStage:=M26,
                     fMaxRMSError=>fMaxYRMSErrorM2K2,
                     fMinRMSError=>fMinYRMSErrorM2K2);
//MR2K2 rX ENC RMS
fbrXRMSErrorM2K2(stMotionStage:=M27,
                     fMaxRMSError=>fMaxrXRMSErrorM2K2,
                     fMinRMSError=>fMinrXRMSErrorM2K2);

//STO for MR2K2 Benders
M25.bHardwareEnable := M2K2FLATbSTOEnable1 AND M2K2FLATbSTOEnable2;
M26.bHardwareEnable := M2K2FLATbSTOEnable1 AND M2K2FLATbSTOEnable2;
M27.bHardwareEnable := M2K2FLATbSTOEnable1 AND M2K2FLATbSTOEnable2;

//Encoder reference positions for MR2K2
nEncRefXM2K2  := ULINT_TO_UDINT(GVL_M2K2.nM2K2X_ENC_REF);
nEncRefYM2K2  := ULINT_TO_UDINT(GVL_M2K2.nM2K2Y_ENC_REF);
nEncRefrXM2K2  := ULINT_TO_UDINT(GVL_M2K2.nM2K2rX_ENC_REF);

// M2K2 Bender Encoder Count Values For Epics
nEncCntXM2K2  := ULINT_TO_UDINT(M25.nRawEncoderULINT);
nEncCntYM2K2  := ULINT_TO_UDINT(M26.nRawEncoderULINT);
nEncCntrXM2K2  := ULINT_TO_UDINT(M27.nRawEncoderULINT);

// Axilon Cooling Panel
fbCoolingPanel();

fbXSetup(stPositionState:=GVL_States.stDefaultKBX, bSetDefault:=TRUE);

fbXSetup(stPositionState:=astCoatingStatesX[E_B4C_Rh_CoatingStates.B4C],
    sName:='B4C',
    sPmpsState:='MR2K2:FLAT-B4C',
    nEncoderCount:=ULINT_TO_UDINT(18446744073704980246),
    fDelta:=5
);
fbXSetup(stPositionState:=astCoatingStatesX[E_B4C_Rh_CoatingStates.Rh],
    sName:='Rh',
    sPmpsState:='MR2K2:FLAT-RHODIUM',
    nEncoderCount:=ULINT_TO_UDINT(18446744073701980417),
    fDelta:=5
);
fbCoatingStates(
    stMotionStage:=Main.M25,
    astPositionState:=astCoatingStatesX,
    eEnumSet:=eStateSet,
    eEnumGet:=eStateGet,
    fbFFHWO:=GVL_PMPS.fbFastFaultOutput2,
    fbArbiter:=GVL_PMPS.fbArbiter2,
    bEnableMotion:=TRUE,
    bEnableBeamParams:=TRUE,
    sDeviceName:='MR2K2:FLAT',
    sTransitionKey:='MR2K2:FLAT-TRANSITION',
);

END_PROGRAM
Related:

PRG_MR3K2_KBH

PROGRAM PRG_MR3K2_KBH
VAR
    // Encoder Arrays/RMS Watch:
        //MR3K2 X ENC RMS
        {attribute 'pytmc' := '     pv: MR3K2:KBH:ENC:X'}
        fbXRMSErrorM3K2 : FB_RMSWatch;
        fMaxXRMSErrorM3K2 : LREAL;
        fMinXRMSErrorM3K2 : LREAL;
        //MR3K2 Y ENC RMS
        {attribute 'pytmc' := '     pv: MR3K2:KBH:ENC:Y'}
        fbYRMSErrorM3K2 : FB_RMSWatch;
        fMaxYRMSErrorM3K2 : LREAL;
        fMinYRMSErrorM3K2 : LREAL;
        //MR3K2 rY ENC RMS
        {attribute 'pytmc' := '     pv: MR3K2:KBH:ENC:PITCH'}
        fbrYRMSErrorM3K2 : FB_RMSWatch;
        fMaxrYRMSErrorM3K2 : LREAL;
        fMinrYRMSErrorM3K2 : LREAL;
        //MR3K2 US ENC RMS
        {attribute 'pytmc' := '     pv: MR3K2:KBH:ENC:BEND:US'}
        fbUSRMSErrorM3K2 : FB_RMSWatch;
        fMaxUSRMSErrorM3K2 : LREAL;
        fMinUSRMSErrorM3K2 : LREAL;
        //MR3K2 DS ENC RMS
        {attribute 'pytmc' := '     pv: MR3K2:KBH:ENC:BEND:DS'}
        fbdSRMSErrorM3K2 : FB_RMSWatch;
        fMaxDSRMSErrorM3K2 : LREAL;
        fMinDSRMSErrorM3K2 : LREAL;

    // Encoder Reference Values
        //MR3K2 X ENC REF
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:X:REF
            field: EGU cnt
            io: i
        '}
        nEncRefXM3K2 : UDINT;
        //MR3K2 Y ENC REF
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:Y:REF
            field: EGU cnt
            io: i
        '}
        nEncRefYM3K2 : UDINT;
        //MR3K2 rY ENC REF
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:PITCH:REF
            field: EGU cnt
            io: i
        '}
        nEncRefrYM3K2 : UDINT;
        //MR3K2 US ENC REF
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:BEND:US:REF
            field: EGU cnt
            io: i
        '}
        nEncRefUSM3K2 : UDINT;
        //MR3K2 DS ENC REF
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:BEND:DS:REF
            field: EGU cnt
            io: i
        '}
        nEncRefDSM3K2 : UDINT;

    // Encoder raw counts
        //M3K2 KBH X ENC CNT
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:X:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntXM3K2 : UDINT;
        //M3K2 KBH Y ENC CNT
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:Y:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntYM3K2 : UDINT;
        //M3K2 KBH rY ENC CNT
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:PITCH:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntrYM3K2 : UDINT;
        //M3K2 KBH US ENC CNT
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:BEND:US:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntUSM3K2 : UDINT;
        //M3K2 KBH DS ENC CNT
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:ENC:BEND:DS:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntDSM3K2 : UDINT;

    // MR3K2 Bender RTDs
        // MR3K2 US RTDs
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:RTD:BEND:US:1
            field: ASLO 0.01
            field: EGU C
            io: i
        '}
        fM3K2US_RTD_1 : REAL;
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:RTD:BEND:US:2
            field: ASLO 0.01
            field: EGU C
            io: i
        '}
        fM3K2US_RTD_2 : REAL;
        // M3K2 DS RTDs
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:RTD:BEND:DS:1
            field: ASLO 0.01
            field: EGU C
            io: i
        '}
        fM3K2DS_RTD_1 : REAL;
        {attribute 'pytmc' := '
            pv: MR3K2:KBH:RTD:BEND:DS:3
            field: ASLO 0.01
            field: EGU C
            io: i
        '}
        fM3K2DS_RTD_3 : REAL;

    // RTD error bit
        bM3K2US_RTD_1_Err AT %I*: BOOL;
        bM3K2US_RTD_2_Err AT %I*: BOOL;

        bM3K2DS_RTD_1_Err AT %I*: BOOL;

        bM3K2DS_RTD_3_Err AT %I*: BOOL;

    //Emergency Stop for MR3K2
        {attribute 'TcLinkTo' := 'TIIB[EL1004_M3K2_STO]^Channel 1^Input'}
        M3K2KBHbSTOEnable1 AT %I* : BOOL;
        {attribute 'TcLinkTo' := 'TIIB[EL1004_M3K2_STO]^Channel 2^Input'}
        M3K2KBHbSTOEnable2 AT %I* : BOOL;
    {attribute 'TcLinkTo' := '.fbFlow_1.iRaw := TIIB[EL3054_MR3_4K2_FWM_PRSM]^AI Standard Channel 1^Value;
                              .fbPress_1.iRaw := TIIB[EL3054_MR3_4K2_FWM_PRSM]^AI Standard Channel 2^Value
    '}
    {attribute 'pytmc' := '
        pv: MR3K2:KBH
    '}
    // MR3K2 Flow Sensors
    fbCoolingPanel : FB_Axilon_Cooling_1f1p;
(*
    // PMPS
        ffBenderRange : FB_FastFault := (
            i_xAutoReset := TRUE,
            i_DevName := 'MR3K2 Bender',
            i_Desc := 'Benders have moved out of range. Adjust bender to be within limits to clear fault',
            i_TypeCode := 16#???);
*)
    {attribute 'pytmc' := 'pv: MR3K2:KBH:COATING'}
    fbCoatingStates: FB_PositionStatePMPS1D;
    {attribute 'pytmc' := '
      pv: MR3K2:KBH:COATING:STATE:SET
      io: io
    '}
    eStateSet: E_B4C_Rh_CoatingStates;
    {attribute 'pytmc' := '
      pv: MR3K2:KBH:COATING:STATE:GET
      io: i
    '}
    eStateGet: E_B4C_Rh_CoatingStates;
    fbYSetup: FB_StateSetupHelper;

    astCoatingStatesY: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3202-0010_M3K2DS2_M3K2DS3]^RTD Inputs Channel 1^Value;
                              .bUnderrange := TIIB[EL3202-0010_M3K2DS2_M3K2DS3]^RTD Inputs Channel 1^Status^Underrange;
                              .bOverrange := TIIB[EL3202-0010_M3K2DS2_M3K2DS3]^RTD Inputs Channel 1^Status^Overrange;
                              .bError := TIIB[EL3202-0010_M3K2DS2_M3K2DS3]^RTD Inputs Channel 1^Status^Error'}
    {attribute 'pytmc' := '
        pv: MR3K2:KBH:RTD:CHIN:R
        field: EGU C
        io: i
    '}
    fbM3K2_Chin_Right_RTD : FB_TempSensor;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3202-0010_M3K2US3_M3K2DS1]^RTD Inputs Channel 1^Value;
                              .bUnderrange := TIIB[EL3202-0010_M3K2US3_M3K2DS1]^RTD Inputs Channel 1^Status^Underrange;
                              .bOverrange := TIIB[EL3202-0010_M3K2US3_M3K2DS1]^RTD Inputs Channel 1^Status^Overrange;
                              .bError := TIIB[EL3202-0010_M3K2US3_M3K2DS1]^RTD Inputs Channel 1^Status^Error'}
    {attribute 'pytmc' := '
        pv: MR3K2:KBH:RTD:CHIN:L
        field: EGU C
        io: i
    '}
    fbM3K2_Chin_Left_RTD : FB_TempSensor;

END_VAR
//FB_Motion stages for MR3K2 Axes
//MR3K2 KBH
fbMotionStageM28(stMotionStage:=M28);
fbMotionStageM29(stMotionStage:=M29);
fbMotionStageM30(stMotionStage:=M30);
fbMotionStageM31(stMotionStage:=M31);
fbMotionStageM32(stMotionStage:=M32);

// Calculate Pitch RMS Error for MR3K2 Axes:
//MR3K2 X ENC RMS
fbXRMSErrorM3K2(stMotionStage:=M28,
                     fMaxRMSError=>fMaxXRMSErrorM3K2,
                     fMinRMSError=>fMinXRMSErrorM3K2);
//MR3K2 X ENC RMS
fbYRMSErrorM3K2(stMotionStage:=M29,
                     fMaxRMSError=>fMaxYRMSErrorM3K2,
                     fMinRMSError=>fMinYRMSErrorM3K2);
//MR3K2 rY ENC RMS
fbrYRMSErrorM3K2(stMotionStage:=M30,
                     fMaxRMSError=>fMaxrYRMSErrorM3K2,
                     fMinRMSError=>fMinrYRMSErrorM3K2);
//MR3K2 US ENC RMS
fbUSRMSErrorM3K2(stMotionStage:=M31,
                     fMaxRMSError=>fMaxUSRMSErrorM3K2,
                     fMinRMSError=>fMinUSRMSErrorM3K2);
//MR3K2 DS ENC RMS
fbDSRMSErrorM3K2(stMotionStage:=M32,
                     fMaxRMSError=>fMaxDSRMSErrorM3K2,
                     fMinRMSError=>fMinDSRMSErrorM3K2);

//STO for MR3K2 Benders
M28.bHardwareEnable := M3K2KBHbSTOEnable1;// AND M3K2KBHbSTOEnable2;
M29.bHardwareEnable := M3K2KBHbSTOEnable1;// AND M3K2KBHbSTOEnable2;
M30.bHardwareEnable := M3K2KBHbSTOEnable1;// AND M3K2KBHbSTOEnable2;
M31.bHardwareEnable := M3K2KBHbSTOEnable1;// AND M3K2KBHbSTOEnable2;
M32.bHardwareEnable := M3K2KBHbSTOEnable1;// AND M3K2KBHbSTOEnable2;

//Encoder reference positions for MR3K2
nEncRefXM3K2  := ULINT_TO_UDINT(GVL_M3K2.nM3K2X_ENC_REF);
nEncRefYM3K2  := ULINT_TO_UDINT(GVL_M3K2.nM3K2Y_ENC_REF);
nEncRefrYM3K2  := ULINT_TO_UDINT(GVL_M3K2.nM3K2rY_ENC_REF);
nEncRefUSM3K2  := ULINT_TO_UDINT(GVL_M3K2.nM3K2US_ENC_REF);
nEncRefDSM3K2  := ULINT_TO_UDINT(GVL_M3K2.nM3K2DS_ENC_REF);

// M3K2 Bender Encoder Count Values For Epics
nEncCntXM3K2  := ULINT_TO_UDINT(M28.nRawEncoderULINT);
nEncCntYM3K2  := ULINT_TO_UDINT(M29.nRawEncoderULINT);
nEncCntrYM3K2  := ULINT_TO_UDINT(M30.nRawEncoderULINT);
nEncCntUSM3K2  := ULINT_TO_UDINT(M31.nRawEncoderULINT);
nEncCntDSM3K2  := ULINT_TO_UDINT(M32.nRawEncoderULINT);

// MR3K2 Bender RTDs
fM3K2US_RTD_1 := INT_TO_REAL(GVL_M3K2.nM3K2US_RTD_1);
fM3K2US_RTD_2 := INT_TO_REAL(GVL_M3K2.nM3K2US_RTD_2);
fM3K2DS_RTD_1 := INT_TO_REAL(GVL_M3K2.nM3K2DS_RTD_1);
fM3K2DS_RTD_3 := INT_TO_REAL(GVL_M3K2.nM3K2DS_RTD_3);

// RTD not connected if T=0
bM3K2US_RTD_1_Err := fM3K2US_RTD_1 = 0;
bM3K2US_RTD_2_Err := fM3K2US_RTD_2 = 0;
bM3K2DS_RTD_1_Err := fM3K2DS_RTD_1 = 0;
bM3K2DS_RTD_3_Err := fM3K2DS_RTD_3 = 0;

// M3K2 Bender RTD interlocks
M31.bHardwareEnable R= fM3K2US_RTD_1 > 9000 OR bM3K2US_RTD_1_Err;
M32.bHardwareEnable R= fM3K2DS_RTD_1 > 9000 OR bM3K2DS_RTD_1_Err;

// Axilon Cooling Panel
fbCoolingPanel();
(*
//PMPS
ffBenderRange.i_xOK :=
    GVL_M3K2.nM3K2US_PMPS_LowerLimit < nEncCntUSM3K2 AND nEncCntUSM3K2 < GVL_M3K2.nM3K2US_PMPS_UpperLimit AND
    GVL_M3K2.nM3K2DS_PMPS_LowerLimit < nEncCntDSM3K2 AND nEncCntUSM3K2 < GVL_M3K2.nM3K2DS_PMPS_UpperLimit;
ffBenderRange(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
*)
fbYSetup(stPositionState:=GVL_States.stDefaultKBY, bSetDefault:=TRUE);

fbYSetup(stPositionState:=astCoatingStatesY[E_B4C_Rh_CoatingStates.B4C],
    sName:='B4C',
    sPmpsState:='MR3K2:KBH-B4C',
    nEncoderCount:=6357520,
    fDelta:=5
);
fbYSetup(stPositionState:=astCoatingStatesY[E_B4C_Rh_CoatingStates.Rh],
    sName:='Rh',
    sPmpsState:='MR3K2:KBH-RHODIUM',
    nEncoderCount:=3857540,
    fDelta:=2.5
);
fbCoatingStates(
    stMotionStage:=Main.M29,
    astPositionState:=astCoatingStatesY,
    eEnumSet:=eStateSet,
    eEnumGet:=eStateGet,
    fbFFHWO:=GVL_PMPS.fbFastFaultOutput2,
    fbArbiter:=GVL_PMPS.fbArbiter2,
    bEnableMotion:=TRUE,
    bEnableBeamParams:=TRUE,
    sDeviceName:='MR3K2:KBH',
    sTransitionKey:='MR3K2:KBH-TRANSITION',
);

fbM3K2_Chin_Right_RTD(fResolution:=0.01);
fbM3K2_Chin_Left_RTD(fResolution:=0.01);

END_PROGRAM
Related:

PRG_MR4K2_KBV

PROGRAM PRG_MR4K2_KBV
VAR
    // Encoder Arrays/RMS Watch:
        //MR4K2 X ENC RMS
        {attribute 'pytmc' := '     pv: MR4K2:KBV:ENC:X'}
        fbXRMSErrorM4K2 : FB_RMSWatch;
        fMaxXRMSErrorM4K2 : LREAL;
        fMinXRMSErrorM4K2 : LREAL;
        //MR4K2 Y ENC RMS
        {attribute 'pytmc' := '     pv: MR4K2:KBV:ENC:Y'}
        fbYRMSErrorM4K2 : FB_RMSWatch;
        fMaxYRMSErrorM4K2 : LREAL;
        fMinYRMSErrorM4K2 : LREAL;
        //MR4K2 rX ENC RMS
        {attribute 'pytmc' := '     pv: MR4K2:KBV:ENC:PITCH'}
        fbrXRMSErrorM4K2 : FB_RMSWatch;
        fMaxrXRMSErrorM4K2 : LREAL;
        fMinrXRMSErrorM4K2 : LREAL;
        //MR4K2 US ENC RMS
        {attribute 'pytmc' := '     pv: MR4K2:KBV:ENC:BEND:US'}
        fbUSRMSErrorM4K2 : FB_RMSWatch;
        fMaxUSRMSErrorM4K2 : LREAL;
        fMinUSRMSErrorM4K2 : LREAL;
        //MR4K2 DS ENC RMS
        {attribute 'pytmc' := '     pv: MR4K2:KBV:ENC:BEND:DS'}
        fbdSRMSErrorM4K2 : FB_RMSWatch;
        fMaxDSRMSErrorM4K2 : LREAL;
        fMinDSRMSErrorM4K2 : LREAL;

    // Encoder Reference Values
        //MR4K2 X ENC REF
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:X:REF
            field: EGU cnt
            io: i
        '}
        nEncRefXM4K2 : UDINT;
        //MR4K2 Y ENC REF
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:Y:REF
            field: EGU cnt
            io: i
        '}
        nEncRefYM4K2 : UDINT;
        //MR4K2 rX ENC REF
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:PITCH:REF
            field: EGU cnt
            io: i
        '}
        nEncRefrXM4K2 : UDINT;
        //MR4K2 US ENC REF
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:BEND:US:REF
            field: EGU cnt
            io: i
        '}
        nEncRefUSM4K2 : UDINT;
        //MR4K2 DS ENC REF
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:BEND:DS:REF
            field: EGU cnt
            io: i
        '}
        nEncRefDSM4K2 : UDINT;

    // Encoder raw counts
        //M4K2 KBV X ENC CNT
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:X:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntXM4K2 : UDINT;
        //M4K2 KBV Y ENC CNT
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:Y:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntYM4K2 : UDINT;
        //M4K2 KBV rX ENC CNT
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:PITCH:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntrXM4K2 : UDINT;
        //M4K2 KBV US ENC CNT
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:BEND:US:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntUSM4K2 : UDINT;
        //M4K2 KBV DS ENC CNT
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:ENC:BEND:DS:CNT
            field: EGU cnt
            io: i
        '}
        nEncCntDSM4K2 : UDINT;

    // MR4K2 Bender RTDs
        // MR4K2 US RTDs
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:RTD:BEND:US:1
            field: ASLO 0.01
            field: EGU C
            io: i
        '}
        fM4K2US_RTD_1 : REAL;
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:RTD:BEND:US:2
            field: ASLO 0.01
            field: EGU C
            io: i
        '}
        fM4K2US_RTD_2 : REAL;
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:RTD:BEND:US:3
            field: ASLO 0.01
            field: EGU C
            io: i
        '}
        fM4K2US_RTD_3 : REAL;

        // M4K2 DS RTDs
        {attribute 'pytmc' := '
            pv: MR4K2:KBV:RTD:BEND:DS:1
            field: ASLO 0.01
            field: EGU C
            io: i
        '}
        fM4K2DS_RTD_1 : REAL;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3204_M4K2_CHIN]^RTD Inputs Channel 1^Value;
                              .bUnderrange := TIIB[EL3204_M4K2_CHIN]^RTD Inputs Channel 1^Status^Underrange;
                              .bOverrange := TIIB[EL3204_M4K2_CHIN]^RTD Inputs Channel 1^Status^Overrange;
                              .bError := TIIB[EL3204_M4K2_CHIN]^RTD Inputs Channel 1^Status^Error'}
    {attribute 'pytmc' := '
        pv: MR4K2:KBV:RTD:CHIN:R
        field: EGU C
        io: i
    '}
    fbM4K2_Chin_Right_RTD : FB_TempSensor;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3204_M4K2_CHIN]^RTD Inputs Channel 2^Value;
                              .bUnderrange := TIIB[EL3204_M4K2_CHIN]^RTD Inputs Channel 2^Status^Underrange;
                              .bOverrange := TIIB[EL3204_M4K2_CHIN]^RTD Inputs Channel 2^Status^Overrange;
                              .bError := TIIB[EL3204_M4K2_CHIN]^RTD Inputs Channel 2^Status^Error'}
    {attribute 'pytmc' := '
        pv: MR4K2:KBV:RTD:CHIN:L
        field: EGU C
        io: i
    '}
    fbM4K2_Chin_Left_RTD : FB_TempSensor;

    // RTD error bit
        bM4K2US_RTD_1_Err AT %I*: BOOL;
        bM4K2US_RTD_2_Err AT %I*: BOOL;
        bM4K2US_RTD_3_Err AT %I*: BOOL;
        bM4K2DS_RTD_1_Err AT %I*: BOOL;
        bM4K2DS_RTD_2_Err AT %I*: BOOL;
        bM4K2DS_RTD_3_Err AT %I*: BOOL;

    //Emergency Stop for MR4K2
        {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^Term 79 (EK1521-0010)^Term 306 (EK1501-0010)^Term 322 (EK1122)^EK1100_MR4K2_BENDER^EL1004_M4K2_STO^Channel 1^Input'}
        M4K2KBVbSTOEnable1 AT %I* : BOOL;
        {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^Term 79 (EK1521-0010)^Term 306 (EK1501-0010)^Term 322 (EK1122)^EK1100_MR4K2_BENDER^EL1004_M4K2_STO^Channel 2^Input'}
        M4K2KBVbSTOEnable2 AT %I* : BOOL;

    // MR4K2 Flow Sensors
    {attribute 'TcLinkTo' := '.fbFlow_1.iRaw := TIIB[EL3054_MR3_4K2_FWM_PRSM]^AI Standard Channel 3^Value;
                              .fbPress_1.iRaw := TIIB[EL3054_MR3_4K2_FWM_PRSM]^AI Standard Channel 2^Value
    '}
    {attribute 'pytmc' := '
        pv: MR4K2:KBV
    '}
    fbCoolingPanel : FB_Axilon_Cooling_1f1p;

(*
    // PMPS
        ffBenderRange : FB_FastFault := (
            i_xAutoReset := TRUE,
            i_DevName := 'MR4K2 Bender',
            i_Desc := 'Benders have moved out of range. Adjust bender to be within limits to clear fault',
            i_TypeCode := 16#???);
*)
    {attribute 'pytmc' := 'pv: MR4K2:KBV:COATING'}
    fbCoatingStates: FB_PositionStatePMPS1D;
    {attribute 'pytmc' := '
      pv: MR4K2:KBV:COATING:STATE:SET
      io: io
    '}
    eStateSet: E_B4C_Rh_CoatingStates;
    {attribute 'pytmc' := '
      pv: MR4K2:KBV:COATING:STATE:GET
      io: i
    '}
    eStateGet: E_B4C_Rh_CoatingStates;
    fbXSetup: FB_StateSetupHelper;

    astCoatingStatesX: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;

END_VAR
//FB_Motion stages for MR4K2 Axes
//MR4K2 KBV
fbMotionStageM33(stMotionStage:=M33);
fbMotionStageM34(stMotionStage:=M34);
fbMotionStageM35(stMotionStage:=M35);
fbMotionStageM36(stMotionStage:=M36);
fbMotionStageM37(stMotionStage:=M37);

// Calculate Pitch RMS Error for MR4K2 Axes:
//MR4K2 X ENC RMS
fbXRMSErrorM4K2(stMotionStage:=M33,
                     fMaxRMSError=>fMaxXRMSErrorM4K2,
                     fMinRMSError=>fMinXRMSErrorM4K2);
//MR4K2 X ENC RMS
fbYRMSErrorM4K2(stMotionStage:=M34,
                     fMaxRMSError=>fMaxYRMSErrorM4K2,
                     fMinRMSError=>fMinYRMSErrorM4K2);
//MR4K2 rX ENC RMS
fbrXRMSErrorM4K2(stMotionStage:=M35,
                     fMaxRMSError=>fMaxrXRMSErrorM4K2,
                     fMinRMSError=>fMinrXRMSErrorM4K2);
//MR4K2 US ENC RMS
fbUSRMSErrorM4K2(stMotionStage:=M36,
                     fMaxRMSError=>fMaxUSRMSErrorM4K2,
                     fMinRMSError=>fMinUSRMSErrorM4K2);
//MR4K2 DS ENC RMS
fbDSRMSErrorM4K2(stMotionStage:=M37,
                     fMaxRMSError=>fMaxDSRMSErrorM4K2,
                     fMinRMSError=>fMinDSRMSErrorM4K2);

//STO for MR4K2 Benders
M33.bHardwareEnable := M4K2KBVbSTOEnable1 AND M4K2KBVbSTOEnable2;
M34.bHardwareEnable := M4K2KBVbSTOEnable1 AND M4K2KBVbSTOEnable2;
M35.bHardwareEnable := M4K2KBVbSTOEnable1 AND M4K2KBVbSTOEnable2;
M36.bHardwareEnable := M4K2KBVbSTOEnable1 AND M4K2KBVbSTOEnable2;
M37.bHardwareEnable := M4K2KBVbSTOEnable1 AND M4K2KBVbSTOEnable2;

//Encoder reference positions for MR4K2
nEncRefXM4K2  := ULINT_TO_UDINT(GVL_M4K2.nM4K2X_ENC_REF);
nEncRefYM4K2  := ULINT_TO_UDINT(GVL_M4K2.nM4K2Y_ENC_REF);
nEncRefrXM4K2  := ULINT_TO_UDINT(GVL_M4K2.nM4K2rX_ENC_REF);
nEncRefUSM4K2  := ULINT_TO_UDINT(GVL_M4K2.nM4K2US_ENC_REF);
nEncRefDSM4K2  := ULINT_TO_UDINT(GVL_M4K2.nM4K2DS_ENC_REF);

// M4K2 Bender Encoder Count Values For Epics
nEncCntXM4K2  := ULINT_TO_UDINT(M33.nRawEncoderULINT);
nEncCntYM4K2  := ULINT_TO_UDINT(M34.nRawEncoderULINT);
nEncCntrXM4K2  := ULINT_TO_UDINT(M35.nRawEncoderULINT);
nEncCntUSM4K2  := ULINT_TO_UDINT(M36.nRawEncoderULINT);
nEncCntDSM4K2  := ULINT_TO_UDINT(M37.nRawEncoderULINT);

// MR4K2 Bender RTDs
fM4K2US_RTD_1 := INT_TO_REAL(GVL_M4K2.nM4K2US_RTD_1);
fM4K2US_RTD_2 := INT_TO_REAL(GVL_M4K2.nM4K2US_RTD_2);
fM4K2US_RTD_3 := INT_TO_REAL(GVL_M4K2.nM4K2US_RTD_3);

fM4K2DS_RTD_1 := INT_TO_REAL(GVL_M4K2.nM4K2DS_RTD_1);


// RTD not connected if T=0
bM4K2US_RTD_1_Err := fM4K2US_RTD_1 = 0;
bM4K2US_RTD_2_Err := fM4K2US_RTD_2 = 0;
bM4K2US_RTD_3_Err := fM4K2US_RTD_3 = 0;
bM4K2DS_RTD_1_Err := fM4K2DS_RTD_1 = 0;

// M4K2 Bender RTD interlocks
M36.bHardwareEnable R= fM4K2US_RTD_1 > 9000 OR bM4K2US_RTD_1_Err;
M37.bHardwareEnable R= fM4K2DS_RTD_1 > 9000 OR bM4K2DS_RTD_1_Err;

fbM4K2_Chin_Right_RTD(fResolution:=0.01);
fbM4K2_Chin_Left_RTD(fResolution:=0.01);

// Axilon Cooling Panel
fbCoolingPanel();
(*
//PMPS
ffBenderRange.i_xOK :=
    GVL_M4K2.nM4K2US_PMPS_LowerLimit < nEncCntUSM4K2 AND nEncCntUSM4K2 < GVL_M4K2.nM4K2US_PMPS_UpperLimit AND
    GVL_M4K2.nM4K2DS_PMPS_LowerLimit < nEncCntDSM4K2 AND nEncCntUSM4K2 < GVL_M4K2.nM4K2DS_PMPS_UpperLimit;
ffBenderRange(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
*)
fbXSetup(stPositionState:=GVL_States.stDefaultKBX, bSetDefault:=TRUE);

fbXSetup(stPositionState:=astCoatingStatesX[E_B4C_Rh_CoatingStates.B4C],
    sName:='B4C',
    sPmpsState:='MR4K2:KBV-B4C',
    nEncoderCount:=5824240,
    fDelta:=5
);
fbXSetup(stPositionState:=astCoatingStatesX[E_B4C_Rh_CoatingStates.Rh],
    sName:='Rh',
    sPmpsState:='MR4K2:KBV-RHODIUM',
    nEncoderCount:=3524222,
    fDelta:=1.5
);
fbCoatingStates(
    stMotionStage:=Main.M33,
    astPositionState:=astCoatingStatesX,
    eEnumSet:=eStateSet,
    eEnumGet:=eStateGet,
    fbFFHWO:=GVL_PMPS.fbFastFaultOutput2,
    fbArbiter:=GVL_PMPS.fbArbiter2,
    bEnableMotion:=TRUE,
    bEnableBeamParams:=TRUE,
    sDeviceName:='MR4K2:KBV',
    sTransitionKey:='MR4K2:KBV-TRANSITION',
);

END_PROGRAM
Related:

PRG_SL1K2_EXIT

PROGRAM PRG_SL1K2_EXIT
VAR
    FFO    :    FB_FastFault :=(
        i_DevName := 'SL1K2-EXIT',
        i_Desc := 'Fault occurs when device is in an usafe state',
        i_TypeCode := 16#E50);
    fbPitch: FB_MotionStage;
    fbRoll: FB_MotionStage;
    fbVertical: FB_MotionStage;
    fbGap: FB_MotionStage;
    fbYag: FB_MotionStage;

     {attribute 'pytmc' := '
        pv: SL1K2:EXIT:YAG:STATE
        io: i
    '}
    fbStates: FB_XS_YAG_States;

    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:RTD:CRYSTAL_TOP
    '}
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3201_SL1K2_1]^RTD^Value;
                              .bError := TIIB[EL3201_SL1K2_1]^RTD^Status^Error;
                              .bUnderrange := TIIB[EL3201_SL1K2_1]^RTD^Status^Underrange;
                              .bOverrange := TIIB[EL3201_SL1K2_1]^RTD^Status^Overrange'}
    RTD_Crystal_TOP: FB_TempSensor;
    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:RTD:CRYSTAL_BOTTOM
    '}
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3201_SL1K2_2]^RTD^Value;
                              .bError := TIIB[EL3201_SL1K2_2]^RTD^Status^Error;
                              .bUnderrange := TIIB[EL3201_SL1K2_2]^RTD^Status^Underrange;
                              .bOverrange := TIIB[EL3201_SL1K2_2]^RTD^Status^Overrange'}
    RTD_Crystal_BOTTOM: FB_TempSensor;

    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:RTD:YAG
    '}
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3201_SL1K2_4]^RTD^Value;
                              .bError := TIIB[EL3201_SL1K2_4]^RTD^Status^Error;
                              .bUnderrange := TIIB[EL3201_SL1K2_4]^RTD^Status^Underrange;
                              .bOverrange := TIIB[EL3201_SL1K2_4]^RTD^Status^Overrange'}
    RTD_YAG: FB_TempSensor;

    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:RTD:HeatSync
    '}
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3201_SL1K2_3]^RTD^Value;
                              .bError := TIIB[EL3201_SL1K2_3]^RTD^Status^Error;
                              .bUnderrange := TIIB[EL3201_SL1K2_3]^RTD^Status^Underrange;
                              .bOverrange := TIIB[EL3201_SL1K2_3]^RTD^Status^Overrange'}
    RTD_HeatSync: FB_TempSensor;


    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:FAN:PWR
        field: ZNAM OFF ;
        field: ONAM ON ;
    '}
     {attribute 'TcLinkTo' := 'TIIB[EL2004_SL1K2]^Channel 1^Output'}
    bFanOn AT %Q*:BOOL;

  {attribute 'pytmc' := '
        pv:  SL1K2:EXIT:LED:PWR
        io: io
        field: ZNAM OFF
        field: ONAM ON
    '}
     {attribute 'TcLinkTo' := 'TIIB[EL2004_SL1K2]^Channel 2^Output'}
    bLEDPower AT %Q*: BOOL;

    {attribute 'pytmc' := '
        pv: SL1K2:EXIT:CAM
    '}
    {attribute 'TcLinkTo' := '.iIlluminatorINT := TIIB[EL4004_SL1K2]^AO Outputs Channel 1^Analog output;
                              .bGigePower := TIIB[EL2004_SL1K2]^Channel 3^Output'}
    fbGige: FB_PPM_Gige;

    {attribute 'pytmc' :='pv: SL1K2:EXIT:FWM:1'}
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3052_SL1K2_FWM]^AI Standard Channel 1^Value'}
    fbFlowMeter: FB_AnalogInput := (iTermBits:=15, fTermMax:=60, fTermMin:=0);

    bInit : BOOL:=TRUE;

     (*Motion Parameters*)
    fSmallDelta: LREAL := 0.01;
    fBigDelta: LREAL := 10;
    fMaxVelocity: LREAL := 0.5;
    fHighAccel: LREAL := 0.8;
    fLowAccel: LREAL := 0.1;

END_VAR
//  init the motion stages parameters
IF ( bInit) THEN
    bInit := FALSE;
    FFO.i_DevName := 'SL1K2-EXIT';
    //Pitch
    Main.M19.bHardwareEnable := TRUE;
    Main.M19.bPowerSelf :=TRUE;
    Main.M19.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
    Main.M19.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
   //Vert
    Main.M20.bHardwareEnable := TRUE;
    Main.M20.bPowerSelf :=TRUE;
    Main.M20.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
    Main.M20.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
    //ROLL
    Main.M21.bHardwareEnable := TRUE;
    Main.M21.bPowerSelf :=TRUE;
    Main.M21.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
    Main.M21.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
    //GAP
    Main.M22.bHardwareEnable := TRUE;
    Main.M22.bPowerSelf :=TRUE;
    Main.M22.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
    Main.M22.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;
    //YAG
    Main.M23.bHardwareEnable := TRUE;
    Main.M23.bPowerSelf :=FALSE;
    Main.M23.nBrakeMode := ENUM_StageBrakeMode.NO_BRAKE;
    Main.M23.nEnableMode := ENUM_StageEnableMode.DURING_MOTION;

    // YAG STATES Parameters
    //fbStates.nTransitionAssertionID := 16#E510;
    //fbStates.nUnknownAssertionID := 16#E500;

    //Out
    fbStates.stOut.fPosition := 0;
    fbStates.stOut.bUseRawCounts := FALSE;
    fbStates.stOut.bValid := TRUE;
    fbStates.stOut.stPMPS.sPmpsState := 'SL1K2:EXIT:YAG-OUT';
    //Yag1
    fbStates.stYag1.fPosition := 21;
    fbStates.stYag1.bUseRawCounts := FALSE;
    fbStates.stYag1.bValid := TRUE;
    fbStates.stYag1.stPMPS.sPmpsState := 'SL1K2:EXIT:YAG-YAG1';
    //Yag2
    fbStates.stYag2.fPosition := 40;
    fbStates.stYag2.bUseRawCounts := FALSE;
    fbStates.stYag2.bValid := TRUE;
    fbStates.stYag2.stPMPS.sPmpsState := 'SL1K2:EXIT:YAG-YAG2';

    //Turn on Fan by default
    bFanOn := TRUE;
END_IF



// Instantiate Function block for all the motion
fbPitch(stMotionStage:=Main.M19);//in Air
fbRoll(stMotionStage:=Main.M20);//in Air
fbVertical(stMotionStage:=Main.M21);//in Air

Main.M22.bLimitBackwardEnable := TRUE;
fbGap(stMotionStage:=Main.M22);//in vacuum
fbYag(stMotionStage:=Main.M23);//in vacuum


//States with PMPS
fbStates(
    fbArbiter:=GVL_PMPS.fbArbiter1,
    fbFFHWO:=GVL_PMPS.fbFastFaultOutput1,
    sPmpsDeviceName:='SL1K2:EXIT:YAG',
    sTransitionKey:='SL1K2:EXIT:YAG-TRANSITION',
    stMotionStage:=Main.M23,
    bEnable:=TRUE,
    stOut:=,
    stYag1:=,
    stYag2:=);

//Camera and Ilumination
fbGige();

//RTDs
RTD_Crystal_TOP(fResolution:=0.01);
RTD_Crystal_BOTTOM(fResolution:=0.01);
RTD_YAG(fResolution:=0.01);
RTD_HeatSync(fResolution:=0.01);

//Cooling
fbFlowMeter();

END_PROGRAM
Related:

PRG_SP1K1_MONO

PROGRAM PRG_SP1K1_MONO
VAR

    // Where is the STO
    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^E5 (EK1122)^X1 SP1K1-MONO(EK1100)^SP1K1-EL1004-E17^Channel 1^Input'}
    bSTOEnable1 AT %I*: BOOL;
    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^E5 (EK1122)^X1 SP1K1-MONO(EK1100)^SP1K1-EL1004-E17^Channel 2^Input'}
    bSTOEnable2 AT %I*: BOOL;


    fbMotionStage_m_pi  :FB_MotionStage;
    fbMotionStage_g_pi  :FB_MotionStage;
    fbMotionStage_m_h   :FB_MotionStage;
    fbMotionStage_g_h   :FB_MotionStage;
    fbMotionStage_s_r   :FB_MotionStage;
    fbMotionStage_s_io  :FB_MotionStage;

    {attribute 'TcLinkTo' := '.Count:=TIIB[m_pi_up_dwn_e]^FB Inputs Channel 1^Position'}
    mpi_upe  AT %I*:  ST_RenishawAbsEnc:=(ref:=0);
    {attribute 'TcLinkTo' := '.Count:=TIIB[g_pi_up_dwn_e]^FB Inputs Channel 1^Position'}
    gpi_upe  AT %I*:  ST_RenishawAbsEnc:=(ref:=0);

    {attribute 'pytmc' := '
        pv: SP1K1:MONO:ENC:M_PI:02
        io: o
    '}
    mpi_upeurad: LREAL;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:ENC:G_PI:02
        io: o
    '}
    gpi_upeurad: LREAL;

    (*RTDs*)
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:01
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 1^Value;
                              .bError := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 1^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 1^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 1^Status^Overrange'}
    RTD1 : FB_TempSensor;

    {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:02
        io: o
    '}
    {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 2^Value;
                              .bError := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 2^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 2^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 2^Status^Overrange'}
    RTD2 : FB_TempSensor;
        {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:03
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 3^Value;
                              .bError := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 3^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 3^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 3^Status^Overrange'}
    RTD3 : FB_TempSensor;
        {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:04
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 4^Value;
                              .bError := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 4^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 4^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EP3204-0002-EP2]^RTD RTDInputs Channel 4^Status^Overrange'}
    RTD4 : FB_TempSensor;

    {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:05
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 1^Value;
                              .bError := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 1^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 1^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 1^Status^Overrange'}
    RTD5 :FB_TempSensor;

    {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:06
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 2^Value;
                              .bError := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 2^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 2^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 2^Status^Overrange'}
    RTD6 :FB_TempSensor;

    {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:07
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 3^Value;
                              .bError := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 3^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 3^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 3^Status^Overrange'}
    RTD7 :FB_TempSensor;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:08
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 4^Value;
                              .bError := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 4^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 4^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EL3204-E15]^RTD Inputs Channel 4^Status^Overrange'}
    RTD8 :FB_TempSensor;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:09
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 1^Value;
                              .bError := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 1^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 1^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 1^Status^Overrange'}
    RTD9 :FB_TempSensor;
        {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:10
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 2^Value;
                              .bError := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 2^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 2^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 2^Status^Overrange'}
    RTD10 :FB_TempSensor;
        {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:11
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 3^Value;
                              .bError := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 3^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 3^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 3^Status^Overrange'}
    RTD11 :FB_TempSensor;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:RTD:12
        io: o
    '}
     {attribute 'TcLinkTo' := '.iRaw := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 4^Value;
                              .bError := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 4^Status^Error;
                              .bUnderrange := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 4^Status^Underrange;
                              .bOverrange := TIIB[SP1K1-EL3204-E16]^RTD Inputs Channel 4^Status^Overrange'}
    RTD12 :FB_TempSensor;




    //////////TODO

    {attribute 'pytmc' := '
        pv: GM:PITCH:fipi_read
        io: i
    '}
    fipi_read: LREAL;

    {attribute 'pytmc' := '
        pv: GM:PITCH:fipi_set
        io: o
    '}
    fipi_set:  LREAL;

    {attribute 'pytmc' := '
        pv: SP1K1:MONO:LED:01:PWR
        io: io
        field: ZNAM OFF
        field: ONAM ON
    '}
    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^E5 (EK1122)^Box 84 (EP2338-0002)^Channel 10^Output'}
    bLEDPower01 AT %Q*: BOOL;

    {attribute 'pytmc' := '
        pv: SP1K1:MONO:LED:02:PWR
        io: io
        field: ZNAM OFF
        field: ONAM ON
    '}
    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^E5 (EK1122)^Box 84 (EP2338-0002)^Channel 12^Output'}
    bLEDPower02 AT %Q*: BOOL;

    {attribute 'pytmc' := '
        pv: SP1K1:MONO:LED:03:PWR
        io: io
        field: ZNAM OFF
        field: ONAM ON
    '}
    {attribute 'TcLinkTo' := 'TIID^Device 1 (EtherCAT)^Term 1 (EK1200)^E5 (EK1122)^Box 84 (EP2338-0002)^Channel 14^Output'}
    bLEDPower03 AT %Q*: BOOL;

    sd_io_FFO    :    FB_FastFault :=(
        i_DevName := 'SP1K1-MONO',
        i_Desc := 'Fault occurs When SP1K1-MONO screw driver motor is not in the out position',
        i_TypeCode := 16#1110);
    sd_io_e_pmps : LREAL:= 74000.29;

        // SP1K1 Flow Press Sensors
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_SP1K1_FWM_PRSM]^AI Standard Channel 1^Value'}
    fSP1K1_Flow_1 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:FWM:1
        field: EGU lpm
        io: i
    '}
    fSP1K1_Flow_1_val : LREAL;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_SP1K1_FWM_PRSM]^AI Standard Channel 2^Value'}
    fSP1K1_Flow_2 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:FWM:2
        field: EGU lpm
        io: i
    '}
    fSP1K1_Flow_2_val : LREAL;
    {attribute 'TcLinkTo' := '.iRaw := TIIB[EL3054_SP1K1_FWM_PRSM]^AI Standard Channel 3^Value'}
    fSP1K1_Press_1 : FB_AnalogInput;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:PRSM:1
        field: EGU bar
        io: i
    '}
    fSP1K1_Press_1_val : LREAL;

     stDefaultGH : ST_PositionState := (
        fDelta:=2000,
        fVelocity:=875.0,
        fAccel:=6923,
        fDecel:=6923,
        bMoveOk:=TRUE,
        bValid:=TRUE,
        bUseRawCounts:=TRUE
    );

    {attribute 'pytmc' := 'pv: SP1K1:MONO:GRATING'}
    fbGratingStates: FB_PositionStatePMPS1D;
    {attribute 'pytmc' := '
      pv: SP1K1:MONO:GRATING:STATE:SET
      io: io
    '}
    eStateSet: E_Grating_States;
    {attribute 'pytmc' := '
      pv: SP1K1:MONO:GRATING:STATE:GET
      io: i
    '}
    eStateGet: E_Grating_States;
    fbGHSetup: FB_StateSetupHelper;

    astGratingStates: ARRAY[1..GeneralConstants.MAX_STATES] OF ST_PositionState;

END_VAR
// SP1K1-MONO
M6.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
M7.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
M8.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
M9.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
M10.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;
M11.bHardwareEnable := bSTOEnable1 AND bSTOEnable2;

fbMotionStage_m_pi  (stMotionStage:=M6);
fbMotionStage_g_pi  (stMotionStage:=M7);
fbMotionStage_m_h  (stMotionStage:=M8);
fbMotionStage_g_h  (stMotionStage:=M9);
fbMotionStage_s_io  (stMotionStage:=M10);
fbMotionStage_s_r  (stMotionStage:=M11);





//S_R with no hardware limit switched
M11.bLimitBackwardEnable := TRUE;
M11.bLimitForwardEnable := TRUE;

mpi_upeurad := ULINT_TO_LREAL(mpi_upe.Count)*0.004505;
gpi_upeurad := ULINT_TO_LREAL(gpi_upe.Count)*0.0066667;

(*RTDs*)
RTD1(fResolution:=0.01);
RTD2(fResolution:=0.01);
RTD3(fResolution:=0.01);
RTD4(fResolution:=0.01);
RTD5();
RTD6();
RTD7();
RTD8();
RTD9();
RTD10();
RTD11();
RTD12();
//Evaluate the encoder reading for the screw driver encoder. Fast fault when it is not out.
// i.e. encoder value must be greater than the out position (75000.29 - 1000u)
IF( M10.Axis.NcToPlc.ActPos < sd_io_e_pmps) THEN
        sd_io_FFO.i_xOK := FALSE;
    ELSE
        sd_io_FFO.i_xOK := TRUE;
END_IF

(*FAST FAULT*)
sd_io_FFO(i_xOK := ,
    i_xReset := ,
    i_xAutoReset :=TRUE,
    io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);

fSP1K1_Flow_1(iTermBits:=15, fTermMax:=5.0427, fTermMin:=0.050472);
fSP1K1_Flow_1_val := fSP1K1_Flow_1.fReal;

fSP1K1_Flow_2(iTermBits:=15, fTermMax:=5.0427, fTermMin:=0.050472);
fSP1K1_Flow_2_val := fSP1K1_Flow_2.fReal;

fSP1K1_Press_1(iTermBits:=15, fTermMax:=4.0, fTermMin:=0);
fSP1K1_Press_1_val := fSP1K1_Press_1.fReal;

fbGHSetup(stPositionState:=stDefaultGH, bSetDefault:=TRUE);

fbGHSetup(stPositionState:=astGratingStates[E_Grating_States.LRG],
    sName:='LRG',
    sPmpsState:='SP1K1:MONO-LRG',
    nEncoderCount:=7599807,
);

fbGHSetup(stPositionState:=astGratingStates[E_Grating_States.Unruled],
    sName:='UNRULED',
    sPmpsState:='SP1K1:MONO-UNRULED',
    nEncoderCount:=10000269,
);

fbGHSetup(stPositionState:=astGratingStates[E_Grating_States.YAG],
    sName:='YAG',
    sPmpsState:='SP1K1:MONO-YAG',
    nEncoderCount:=16000506,
);

fbGHSetup(stPositionState:=astGratingStates[E_Grating_States.MEG],
    sName:='MEG',
    sPmpsState:='SP1K1:MONO-MEG',
    nEncoderCount:=25420841,
);

fbGHSetup(stPositionState:=astGratingStates[E_Grating_States.HEG],
    sName:='HEG',
    sPmpsState:='SP1K1:MONO-HEG',
    nEncoderCount:=42600635,
);

fbGHSetup(stPositionState:=astGratingStates[E_Grating_States.LEG],
    sName:='LEG',
    sPmpsState:='SP1K1:MONO-LEG',
    nEncoderCount:=59802448,
);

fbGratingStates(
    stMotionStage:=Main.M9,
    astPositionState:=astGratingStates,
    eEnumSet:=eStateSet,
    eEnumGet:=eStateGet,
    fbFFHWO:=GVL_PMPS.fbFastFaultOutput1,
    fbArbiter:=GVL_PMPS.fbArbiter1,
    bEnableMotion:=TRUE,
    bEnableBeamParams:=TRUE,
    sDeviceName:='SP1K1:MONO',
    sTransitionKey:='SP1K1:MONO-TRANSITION'
);

END_PROGRAM
Related:

PRG_SPO_PMPS

PROGRAM PRG_SPO_PMPS
VAR
    // PMPS
    ffMR1K1Height : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'MR1K1 Height',
        i_Desc := 'MR1K1 Height is out of Single Point Operation safe range',
        i_TypeCode := 16#F501);
    ffMR1K1Pitch : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'MR1K1 Pitch',
        i_Desc := 'MR1K1 Pitch is out of Single Point Operation safe range',
        i_TypeCode := 16#F502);

    ffSP1K1MPitch : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'SP1K1 Mirror Pitch',
        i_Desc := 'SP1K1 Mirror Pitch is out of Single Point Operation safe range',
        i_TypeCode := 16#F503);
    ffSP1K1GPitch : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'SP1K1 Grating Pitch',
        i_Desc := 'SP1K1 Grating Pitch is out of Single Point Operation safe range',
        i_TypeCode := 16#F504);
    ffSP1K1GHorisontal : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'SP1K1 Grating horizontal',
        i_Desc := 'SP1K1 horizontal Pitch is out of Single Point Operation safe range',
        i_TypeCode := 16#F505);

    ffZos : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'T1K1 ZOS',
        i_Desc := 'ST1K1 ZOS is out of Single Point Operation safe range',
        i_TypeCode := 16#F506);
END_VAR
VAR CONSTANT
    nUpperMR1K1Height : UDINT := 32961458; //POS 32911458+ Delta 50000;
    nLowerMR1K1Height : UDINT := 32861458; //POS 32911458- Delta 50000;

    nUpperMR1K1Pitch : UDINT := 10114377;// 10093885 + 20491;
    nLowerMR1K1Pitch : UDINT := 10073393; //10093885 - 20491;

    nUpperSP1K1MPitch : UDINT := 31769145;// 31758046 + 11098;
    nLowerSP1K1MPitch : UDINT := 31746947;//31758046 - 11098;

    nUpperSP1K1MPitch_400 : UDINT := 31285238;
    nLowerSP1K1MPitch_400 : UDINT := 31263041;

    nUpperSP1K1MPitch_1000 : UDINT := 31809544;
    nLowerSP1K1MPitch_1000 : UDINT := 31787347;

    nUpperSP1K1GPitch_700 : UDINT := 23624881;
    nUpperSP1K1GPitch_400 : UDINT := 23204883;
    nUpperSP1K1GPitch_1000 : UDINT := 23564882;

    nUpperSP1K1GH: UDINT := 40000000;
    nLowerSP1K1GH: UDINT := 24000000;

    nZOSLower : UDINT := 22926596; // value still needs to be verified


END_VAR
IF (GVL_PMPS.rPhotonEnergy < 715) AND (GVL_PMPS.rPhotonEnergy >685) THEN
    ffSP1K1MPitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ((M6.nEncoderCount > nLowerSP1K1MPitch) AND (M6.nEncoderCount < nUpperSP1K1MPitch));
    ffSP1K1GPitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ((M7.nEncoderCount < nUpperSP1K1GPitch_700));
ELSIF (GVL_PMPS.rPhotonEnergy < 410) AND (GVL_PMPS.rPhotonEnergy >390) THEN
    ffSP1K1MPitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ((M6.nEncoderCount > nLowerSP1K1MPitch_400) AND (M6.nEncoderCount < nUpperSP1K1MPitch_400));
    ffSP1K1GPitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ((M7.nEncoderCount < nUpperSP1K1GPitch_400));
ELSIF (GVL_PMPS.rPhotonEnergy < 1020) AND (GVL_PMPS.rPhotonEnergy >980) THEN
    ffSP1K1MPitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ((M6.nEncoderCount > nLowerSP1K1MPitch_1000) AND (M6.nEncoderCount < nUpperSP1K1MPitch_1000));
    ffSP1K1GPitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ((M7.nEncoderCount < nUpperSP1K1GPitch_1000));
ELSE
    ffSP1K1MPitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK;
    ffSP1K1GPitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK;

END_IF


//MR1K1 height
ffMR1K1Height.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ((M12.nEncoderCount > nLowerMR1K1Height ) AND (M12.nEncoderCount < nUpperMR1K1Height ));
ffMR1K1Height(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
//MR1K1 pitch
ffMR1K1Pitch.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ( (M16.nEncoderCount > nLowerMR1K1Pitch) AND ( M16.nEncoderCount < nUpperMR1K1Pitch));
ffMR1K1Pitch(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
//SP1K1 mirror pitch
ffSP1K1MPitch(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
//SP1K1 grating pitch
ffSP1K1GPitch(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
//SP1K1 Grating horizontal
ffSP1K1GHorisontal.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR ((M9.nEncoderCount > nLowerSP1K1GH) AND (M9.nEncoderCount < nUpperSP1K1GH));
ffSP1K1GHorisontal(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
//ST1K1 ZOS
ffZos.i_xOK := PRG_MR1K1_BEND_BENDER.ffBenderRange.i_xOK OR (M24.nEncoderCount <= nZOSLower); //encoder counts decreases as value in mm increases
ffZos(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);

END_PROGRAM
Related:

PRG_ST1K1_ZOS

PROGRAM PRG_ST1K1_ZOS
VAR
    fbZOS: FB_MotionStage;
END_VAR
Main.M24.bPowerSelf := TRUE;
Main.M24.bHardwareEnable := TRUE;
fbZOS(stMotionStage:=Main.M24);

END_PROGRAM
Related:

PRG_Stats

PROGRAM PRG_Stats
VAR
    // SP1K1 Grating Mono Vibration Stats
    fGpiEncoderPosDiff: LREAL;
    afGpiPosDiffBuffer: ARRAY[1..1000] OF LREAL;
    afGpiExtraBuffer: ARRAY[1..1000] OF LREAL;
    fbGpiPosDiffCollect: FB_DataBuffer;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:MMS:G_PI:ENCDIFF
    '}
    fbGpiPosDiffStats: FB_BasicStats;

    fGpiRangeMax: LREAL;
    rtNewGpiMove: R_TRIG;
    tonNewGpiMove: TON;


    // SP1K1 Mirror Pitch Mono Vibration Stats
    fMpiEncoderPosDiff: LREAL;
    afMpiPosDiffBuffer: ARRAY[1..10000] OF LREAL;
    afMpiExtraBuffer: ARRAY[1..10000] OF LREAL;
    fbMpiPosDiffCollect: FB_DataBuffer;
    {attribute 'pytmc' := '
        pv: SP1K1:MONO:MMS:M_PI:ENCDIFF
    '}
    fbMpiPosDiffStats: FB_BasicStats;
END_VAR
IF rtNewGpiMove.Q THEN
    // Reset before a move
    fGpiRangeMax := 0;
ELSIF tonNewGpiMove.Q AND ABS(M7.fPosition - M7.stAxisStatus.fActPosition) > 5 THEN
    // Update only during moves, not at the start or end
    fGpiRangeMax := MAX(fGpiRangeMax, fbGpiPosDiffStats.fRange);
END_IF

fGpiEncoderPosDiff := M7.nEncoderCount - (M7.Axis.NcToPlc.SetPos - M7.stAxisParameters.fEncOffset) * 150;
fbGpiPosDiffCollect(
    bExecute:=TRUE,
    pInputAdr:=ADR(fGpiEncoderPosDiff),
    iInputSize:=SIZEOF(fGpiEncoderPosDiff),
    iElemCount:=1000,
    pPartialAdr:=ADR(afGpiPosDiffBuffer),
    pOutputAdr:=ADR(afGpiExtraBuffer),
);
fbGpiPosDiffStats(
    aSignal:=afGpiPosDiffBuffer,
    bAlwaysCalc:=TRUE,
);
rtNewGpiMove(CLK:=M7.bExecute);
tonNewGpiMove(
    IN:=M7.bExecute,
    PT:=T#15s,
);

(*fMpiEncoderPosDiff := M6.nEncoderCount - (M6.Axis.NcToPlc.SetPos - M6.stAxisParameters.fEncOffset) / 0.004505;
fbMpiPosDiffCollect(
    bExecute:=TRUE,
    pInputAdr:=ADR(fMpiEncoderPosDiff),
    iInputSize:=SIZEOF(fMpiEncoderPosDiff),
    iElemCount:=1000,
    pPartialAdr:=ADR(afMpiPosDiffBuffer),
    pOutputAdr:=ADR(afMpiExtraBuffer),
);
fbMpiPosDiffStats(
    aSignal:=afMpiPosDiffBuffer,
    bAlwaysCalc:=TRUE,
);*)

END_PROGRAM

PRG_ZeroOrder_PMPS

PROGRAM PRG_ZeroOrder_PMPS
VAR
// PMPS
    ffZeroOrderBeam : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'Zero Order Beam',
        i_Desc := 'Faults when Zero order beam safe conditions are not met',
        i_TypeCode := 16#F506);
    {attribute 'pytmc' := '
        pv: @(PREFIX)SafeBenderRange
        field: ZNAM FALSE
        field: ONAM TRUE
    '}
    bSafeBenderRange : BOOL;

    {attribute 'pytmc' := '
        pv: @(PREFIX)LRG_Grating_IN
        field: ZNAM FALSE
        field: ONAM TRUE
    '}
    bLRG_Grating_IN : BOOL;

    {attribute 'pytmc' := '
        pv: @(PREFIX)ZOS_IN
        field: ZNAM FALSE
        field: ONAM TRUE
    '}
    bZOS_IN : BOOL;

    {attribute 'pytmc' := '
        pv: @(PREFIX)ZOB_on_Lower_Stopper
        field: ZNAM FALSE
        field: ONAM TRUE
    '}
    bZOB_on_Lower_Stopper : BOOL;

    {attribute 'pytmc' := '
        pv: @(PREFIX)MR1K1_Inserted
        field: ZNAM FALSE
        field: ONAM TRUE
    '}
    bMR1K1_Inserted : BOOL;



    {attribute 'pytmc' := '
        pv: @(PREFIX)MachineMode
        field: ZRST NC
        field: ONST SC
        field: TWST Misconfig
        field: THST UnInit
    '}
    nMachineMode : INT;

    {attribute 'pytmc' := '
        pv: @(PREFIX)BeamPermitted
        field: ZNAM FALSE
        field: ONAM TRUE
    '}
    bBeamPermitted : BOOL;


    Pm3 : LREAL;
    Pm2: LREAL;
    Pm1: LREAL;
    Hm1: LREAL;
    HZos:LREAL;
    Zbm1: LREAL;
    Zbm2: LREAL;
    Zbm3: LREAL;
    Hbm1: LREAL;
    Hbm2: LREAL;
    Hbm3: LREAL;
    Hb0m3:LREAL;
    //fixed calc
    Hm3      : LREAL;
    Hpiv : LREAL;
    Theta_m1: LREAL;
    Theta_m2: LREAL;
    Theta_m3: LREAL;
    {attribute 'pytmc' := '
        pv: @(PREFIX)Delta
        field: ZNAM FALSE
        field: ONAM TRUE
    '}
    Delta : LREAL;
    Ans : LREAL;
    ffZeroOrderBeamExitSlits : FB_FastFault := (
        i_xAutoReset := TRUE,
        i_DevName := 'Zero Order Beam and Exit Slits',
        i_Desc := 'Faults when Zero order beam is aligned and exit slits are closed',
        i_TypeCode := 16#F506);
END_VAR
VAR CONSTANT
    Hi2 :LREAL:=1.4;//m
    Zi2: LREAL :=731.613;//m
    Theta0 : LREAL:=0;
    Zm1 : LREAL:=733.772;//m
    Zmon : LREAL:=739.622;//m
    Zpiv : LREAL:= 739.762 ; //m
    Zzos : LREAL:= 741.422;//m
    Pm1Offset: LREAL:= 18081.1;
    Pm2Offset: LREAL:= -90603;
    Pm3Offset: LREAL:= -63300;//urad, could depend on the inserted grating
END_VAR
//Diagnostics
bSafeBenderRange := SafeBenderRange();
nMachineMode := PMPS_GVL.stCurrentBeamParameters.nMachineMode;
bMR1K1_Inserted := MR1K1_Inserted();

//TRUE if Hzos - hb0m3(Zzoz)  > D
Ans := (Hzos*1E3) - Hb0m3; //um
Delta := Ans;
IF GVL_PMPS.rPhotonEnergy <550 THEN
    bZOB_on_Lower_Stopper := Ans > 3*1E3;
    ELSIF GVL_PMPS.rPhotonEnergy <1000 THEN
        bZOB_on_Lower_Stopper := Ans >  2.5*1E3;
    ELSIF GVL_PMPS.rPhotonEnergy >=1000 THEN
        bZOB_on_Lower_Stopper := Ans >  2*1E3;
END_IF


ffZeroOrderBeam.i_xOK := (SafeBenderRange() AND (((PMPS_GVL.stCurrentBeamParameters.nMachineMode =1(*SC*)) AND (PMPS_GVL.stCurrentBeamParameters.nBeamClass <=6)) OR (PMPS_GVL.stCurrentBeamParameters.nMachineMode =0(*NC*))))
                OR (bLRG_Grating_IN AND bZOS_IN AND bZOB_on_Lower_Stopper AND MR1K1_Inserted()(*NC or SC Any Beamclass with beam at ZOS*));
ffZeroOrderBeam(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
bBeamPermitted := ffZeroOrderBeam.i_xOK;

//
ACT_CALC();
ACT_ZOS_IN();
ACT_LRG_Grating_IN();
ACT_ExitSlitsGap();

END_PROGRAM

ACTION ACT_CALC:
///calc
//Hm1 = 1400000+mr1k1 ben Y up
Hm1:= 1400000+M12.stAxisStatus.fActPosition; //um
//Pm1 = Pm1Offset + mr1k1 bend pitch
Pm1 := Pm1Offset + Main.M16.stAxisStatus.fActPosition; //urad
//Hpiv = 1.625427 is correction from nominal
Hpiv:= (1.625247)*1E6; //um
//Pm2 = mono mirror pitch + Pm2Offset urad
Pm2 := Main.M6.stAxisStatus.fActPosition + Pm2Offset;//urad
//Hm3 = Hpiv + 0.006 m
Hm3:= Hpiv + (0.006*1E6); //um
//Pm3 = mono grating pitch + Pm3Offset urad
Pm3 := Main.M7.stAxisStatus.fActPosition + Pm3Offset; //urad
//Hzos = 1824.3 + st1k1_zos mm
Hzos:= 1824.3 + M24.stAxisStatus.fActPosition; //mm


//urad
Theta_m1 := 2*Pm1 - Theta0;
Theta_m2 := 2*Pm2 - Theta_m1;
Theta_m3 := 2 *Pm3 - 2 * Pm2 + 2* Pm1 - Theta0;

// fix units
//um
Zbm1 := (Hm1 - TAN(Pm1*1E-6)* (Zm1*1E6) - (Hi2*1E6) + TAN(Theta0*1E-6) *(Zi2*1E6))/(TAN(Theta0*1E-6) - TAN(Pm1*1E-6));
hbm1 := TAN(Pm1*1E-6)*Zbm1 + Hm1 - TAN(Pm1*1E-6)*(Zm1*1E6);
//u?
Zbm2 := (Hbm1 - TAN(Theta_m1*1E-6)*Zbm1 - Hpiv + TAN(Pm2*1E-6)*(Zpiv*1E6))/ (TAN(Pm2*1E-6) - TAN(Theta_m1*1E-6));
Hbm2 := TAN(Pm2*1E-6)*Zbm2 + Hpiv - TAN(Pm2*1E-6)*(Zpiv*1E6);

Zbm3 := (Hbm2 - TAN(Theta_m2*1E-6)*Zbm2 - Hm3 + TAN(Pm3*1E-6)*(Zpiv*1E6))/ (TAN(Pm3*1E-6) - TAN(Theta_m2*1E-6));
Hbm3 := TAN(Pm3*1E-6) * Zbm3 + Hm3 - TAN(Pm3*1E-6)*(Zpiv*1E6);

Hb0m3 := TAN(Theta_m3*1E-6)*(Zzos*1E6) + Hbm3 - TAN(Theta_m3*1E-6)*Zbm3;
END_ACTION

ACTION ACT_ExitSlitsGap:
// FAULT condition: Hzos -1.0mm <= Hb0m3) AND sl1k2 gap <= 8.0 mm
ffZeroOrderBeamExitSlits.i_xOK := NOT( (( (Hzos*1E3) - 1) <= Hb0m3) AND ( M22.stAxisStatus.fActPosition <= 8000(*um*)));
ffZeroOrderBeamExitSlits(io_fbFFHWO := GVL_PMPS.fbFastFaultOutput1);
END_ACTION

ACTION ACT_LRG_Grating_IN:
bLRG_Grating_IN := ((M9.stAxisStatus.fActPosition) >= 36000) AND ((M9.stAxisStatus.fActPosition) <= 43000);
END_ACTION

ACTION ACT_ZOS_IN:
IF GVL_PMPS.rPhotonEnergy >1500 THEN bZOS_IN:= FALSE;
    ELSIF (GVL_PMPS.rPhotonEnergy >1000 AND M24.stAxisStatus.fActPosition>= 8.5) THEN bZOS_IN:= TRUE;
        ELSIF (GVL_PMPS.rPhotonEnergy <=1000 AND GVL_PMPS.rPhotonEnergy >550 AND M24.stAxisStatus.fActPosition>= 7.9) THEN bZOS_IN:= TRUE;
            ELSIF (GVL_PMPS.rPhotonEnergy <=550 AND GVL_PMPS.rPhotonEnergy >200 AND M24.stAxisStatus.fActPosition>= 6.9) THEN bZOS_IN:= TRUE;
                ELSE bZOS_IN:= FALSE;
END_IF
END_ACTION

METHOD MR1K1_Inserted : BOOL
VAR_INPUT
END_VAR
MR1K1_Inserted := PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.MR1K1_IN]
                    AND NOT PMPS_GVL.stCurrentBeamParameters.aVetoDevices[PMPS.K_Stopper.MR1K1_OUT];
END_METHOD

METHOD SafeBenderRange : BOOL
VAR_INPUT
END_VAR
SafeBenderRange :=  GVL_M1K1_BENDER_Constants.nM1K1bendUS_PMPS_LowerLimit < M17.nEncoderCount AND M17.nEncoderCount < GVL_M1K1_BENDER_Constants.nM1K1bendUS_PMPS_UpperLimit AND
    GVL_M1K1_BENDER_Constants.nM1K1bendDS_PMPS_LowerLimit < M18.nEncoderCount AND M18.nEncoderCount < GVL_M1K1_BENDER_Constants.nM1K1bendDS_PMPS_UpperLimit;
END_METHOD
Related: