DUTs
E_ArbDosTestStates
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_ArbDosTestStates :
(
Init := 0,
SecondReq := 100,
WaitForAck := 200,
WaitFor2ndAck := 300,
RunAsserts := 8000
);
END_TYPE
- Related:
E_BPTMState
TYPE E_BPTMState :
(
Init := 0,
NewTarget := 1000,
RequestBP := 1500,
WaitForBP := 2500,
WaitingForTransitionAssertion := 2000,
WaitingForFinalAssertion := 3000,
Transitioning := 4000,
WaitForFinalBP := 5000,
CleaningUp := 6000,
Idle := 10000,
Done := 8000,
Error := 9000
);
END_TYPE
E_BPTMTestStates
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_BPTMTestStates :
(
Init := 0,
WaitingForValidID := 50,
WaitingForTransitionAssertion := 100,
StalledAtTransition := 150,
WaitingForFinalAssertion := 200,
WaitingForBeam := 250,
Transitioning := 300,
CleaningUp := 400,
AnotherState := 500,
Retry := 1000,
Done := 8000,
Error := 9000
);
END_TYPE
E_MPSBeamClasses
TYPE E_MPSBeamClasses :
(
Beam_Off := 0,
Kicker_STBY := 1,
BC1Hz := 2,
BC10Hz := 3,
Diagnostic := 4,
BC120Hz := 5,
Tuning := 6,
MAP_1 := 7,
MAP_5 := 8,
MAP_10 := 9,
MAP_25 := 10,
MAP_50 := 11,
MAP_100 := 12,
FULL := 13,
SPARE1 := 14,
SPARE2 := 15
);
END_TYPE
E_MPSBeamRates
TYPE E_MPSBeamRates :
(
MPSRateInvalid := 0,
MPSRate0Hz := 1,
(* Previously used 2 and 3 for single shot and burst *)
MPSRate1Hz := 4,
MPSRate10Hz := 5,
MPSRate30Hz := 6,
MPSRate60Hz := 7,
MPSRate120Hz := 8,
MPSRateUnknown := 9,
MPSRateSingleShot := 10,
MPSRateBurstMode := 11,
MPSRateBurstModeNotActive := 12,
MPSNumberOfBeamRates := 13,
MPSRateBurstInvalid := 14
)UINT;
END_TYPE
K_Apertures
{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE K_Apertures :
(
SL1K0 := 1,
SL2K0 := 2
);
END_TYPE
K_Attenuators
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE K_Attenuators :
(
AT1K0 := 1, // FEE Gas Attenuator
AT1K4 := 2, // TMO Solid Attenuator in the FEE
AT1K2 := 3, // RIX Solid Attenuator
AT2K2 := 4, // RIX Solid Attenuator
AT1K3 := 5 // TXI Solid Attenuator
);
END_TYPE
K_Stopper
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE K_Stopper :
(
ST3K4 := 1, // TMO PPS stopper in
ST1K2 := 2, // RIX PPS stopper in
MR1K1_IN := 3, // Monochromator focus optic vertical state
MR1K1_OUT := 4, // Monochromator focus optic vertical state
MR1K3_IN := 5, // TXI MR1K3 Offset Mirror horizontal state
MR1K3_OUT := 6, // TXI MR1K3 Offset Mirror horizontal state
ST4K4 := 7, //TMO ST4K4 Photon Terminator In Limit Switch
ST1K4 := 8, //TMO Photon Stopper in the FEE
ST1K3 :=9, //TXI Kline PPS stopper in
MR2K3_IN := 10, // TXI MR2K3 Offset Mirror state
MR2K3_OUT := 11, // TXI MR2K3 Offset Mirror horizontal state
DEFAULT := PMPS_GVL.MAX_VETO_DEVICES // DO NOT USE FOR ANYTHING
);
END_TYPE
- Related:
L_Apertures
{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE L_Apertures :
(
SL1L0 := 1,
SL2L0 := 2
);
END_TYPE
L_Attenuators
{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE L_Attenuators :
(
AT1L0 := 1, // FEE Combo Gas/Solid Attenuator
AT2L0 := 2 // FEE Solid Attenuator
);
END_TYPE
L_Stopper
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE L_Stopper :
(
ST1L0 := 1, // ST1
ST1L1 := 2, // TXI PPS Stopper in
MR1L0_L0 := 3, // MR1L0 Pitch state delivery to L0 line
MR1L0_TXI := 4, //MR1L0 Pitch state delivery to TXI line
MR1L1_IN:= 5, // TXI Offset Mirror horizontal state
MR1L1_OUT:= 6, // TXI Offset Mirror horizontal state
DEFAULT := PMPS_GVL.MAX_VETO_DEVICES // DO NOT USE FOR ANYTHING
);
END_TYPE
- Related:
PMPS_CODES
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE PMPS_CODES :
(
FAST_FAULT := 16#1, // Generic fast fault
PEW_FAULT := 16#7, // Fault occurs when the calculated machine photon energy (K value calculated by undulator gap, and electron energy) falls outside the permitted range.
// Arbiter codes
//////////////////////////////////
ARB_FULL := 16#201, // Arbiter pool is full.
//BPTM Codes
//////////////////////////////////
BadTargetID := 16#300,
BadTransID := 16#301,
TransAssrtFail := 16#302,
FinalAssrtFail := 16#303,
NoRoomInArb := 16#304
) UINT;
END_TYPE
ST_AttenuatorStatus
TYPE ST_AttenuatorStatus :
STRUCT
AtTarget : BIT; //0
Moving : BIT; //1
LocalMode : BIT; //2 (* means the attenuator ignores PMPS preemptive requests and operates from a local setpoint *)
PMPSMode : BIT; //3 (* means the attenuator is only taking setpoints from the pmps *)
OK : BIT; //4
Toggle : BIT; //5 just here for fun
Include : BIT; //6 Consider this device status
END_STRUCT
END_TYPE
ST_BeamParams
TYPE ST_BeamParams :
STRUCT
(* Requested pre-optic attenuation % *)
{attribute 'pytmc' := 'pv: Transmission
io: i
field: HOPR 1;
field: LOPR 0;
field: PREC 2;
'}
nTran : REAL := 0;
(* Pulse-rate *)
{attribute 'pytmc' := 'pv: Rate
io: i
field: EGU Hz
'}
nRate : UDINT := 0;
(* Photon energy ranges *)
{attribute 'pytmc' := 'pv: eVRanges
io: i
field: EGU eV'}
{attribute 'displaymode' := 'binary'}
neVRange : DWORD := 0;
(* Photon energy *)
{attribute 'pytmc' := 'pv: PhotonEnergy
io: i
field: EGU eV'}
neV : REAL := 0;
(* Beamclass ranges *)
{attribute 'pytmc' := 'pv: BeamClassRanges
io: i'}
{attribute 'displaymode' := 'binary'}
nBCRange : WORD := 0;
(* Beamclass *)
{attribute 'pytmc' := 'pv: BeamClass
io: i'}
nBeamClass : USINT :=0;
(* Machine Mode *)
{attribute 'pytmc' := 'pv: MachineMode
io: i'}
nMachineMode: USINT:=3;
(* Beamline attenuators *)
{attribute 'pytmc' := 'pv: AuxAtt
io: i'}
astAttenuators : ARRAY [1..PMPS_GVL.AUX_ATTENUATORS] OF ST_PMPS_Attenuator;
(* Stoppers *)
{attribute 'pytmc' := 'pv: Veto
io: i'}
aVetoDevices : ARRAY [1..PMPS_GVL.MAX_VETO_DEVICES] OF BOOL;
(* Apertures *)
{attribute 'pytmc' := 'pv: Apt
io: i'}
astApertures : ARRAY [1..PMPS_GVL.MAX_APERTURES] OF ST_PMPS_Aperture := [(Width:=1E3, Height:=1E3), (Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3)];
// Toggle for watchdog
xValidToggle : BOOL;
// Beam parameter set is valid (if readback), or acknowledged (if request)
{attribute 'pytmc' := 'pv: Valid
io: i'}
xValid : BOOL;
// Cohort index. Identifies which cohort this BP set was included in arbitration
{attribute 'pytmc' := 'pv: Cohort
io: i
field: DESC Cohort inc on each arb cycle
'}
nCohortInt : UDINT;
END_STRUCT
END_TYPE
- Related:
ST_BP_ArbInternal
TYPE ST_BP_ArbInternal EXTENDS ST_BeamParams :
STRUCT
{attribute 'pytmc' := 'pv: ID
io: i
'}
nId : DWORD;
{attribute 'pytmc' := 'pv: Live
io: i
'}
LiveInTable : BOOL;
{attribute 'pytmc' := 'pv: Device
io: i
'}
sDevName: STRING;
END_STRUCT
END_TYPE
- Related:
ST_DbStateParams
TYPE ST_DbStateParams :
// Defines The parameters that are loaded from the json db for every state name
STRUCT
// PMPS database lookup name for this state
{attribute 'pytmc' := '
pv: PMPS_STATE
io: i
field: DESC PMPS Database Lookup Key
'}
sPmpsState: STRING;
// Beam parameters associated with this state
{attribute 'pytmc' := '
pv: BP
io: i
'}
stBeamParams: ST_BeamParams := PMPS_GVL.cst0RateBeam;
// Set to TRUE once the PMPS library has loaded a valid state from the database
{attribute 'pytmc' := '
pv: PMPS_LOADED
io: i
field: DESC TRUE if PMPS loaded parameters from the database.
'}
bBeamParamsLoaded: BOOL;
// Reactive parameters associated with this state
{attribute 'pytmc' := '
pv: ReParams
io: i
'}
stReactiveParams : ST_ReactiveParams;
// Transition ID associated with this state
{attribute 'pytmc' := '
pv: PMPS_ID
io: i
field: DESC Assertion Request ID
'}
nRequestAssertionID: UDINT;
END_STRUCT
END_TYPE
- Related:
ST_Device
TYPE ST_Device :
STRUCT
//IO
stAxis : AXIS_REF;
//The following limit switches must be TRUE or high (ie. NC) to enable that direction of motion
//The EPICS display should show an inverted indicator to communicate end of travel reached
i_xLoLim AT %I* : BOOL; //Wire normally-closed
i_xHiLim AT %I* : BOOL; //Wire normally-closed
//Acuator Parameters
lrUpperPositionLimit : LREAL := 10000000000; //Use in conjuntion with limit switches or set to a huge number to ignore
lrLowerPositionLimit : LREAL := -10000000000; //Use in conjuntion with limit switches or set to a huge number to ignore
xEnable : BOOL := TRUE;
//Supervisory System
//MPS Override
xOverrideMPSLimits : BOOL := FALSE; //Set true to move beyond MPS limits. Note this will trip beam off.
//Status and Faults
xClearFaults : BOOL; //Rising edge tries to clear faults of all kinds
xGovernorFault : BOOL; //Indicates governor is faulted
xGovernorOverridden : BOOL; //Indicates governor is in override mode
xSMFault : BOOL; //Indicates component state machine is faulted
//Device States
StateTable : FB_LinearDeviceStateTable;
stTransitionState : ST_DeviceState; //The transition state might have non-zero beam parameters
nSafeState : UDINT; //State where the device is considered safe
nTransitionState : UDINT;
nRequestedState : UDINT; //EPICS control, cleaned (checked against array limits) and deposited in nTargetState
nTargetState : UDINT; //The requested state should be deposited here after cleaning
nActiveState : UDINT; //The last successfully reached state
END_STRUCT
END_TYPE
- Related:
ST_DeviceState
TYPE ST_DeviceState :
STRUCT
//State name
sStateName : STRING;
nStateRef : UDINT; //Primary key of device state database
//////////////////
// This stuff may belong in a sub-class or something
//Position
rPosition : REAL := 0; //Center of the state
//Tolerance
rTolerance : REAL := 0; //+/- amount
//////////////////
//Required beam parameters
stReqBeamParam : ST_BeamParams;
END_STRUCT
END_TYPE
- Related:
ST_DeviceStateExt
//Use when working with states in code
//Extended with a valid boolean to indicate the structure was loaded
//properly
TYPE ST_DeviceStateExt EXTENDS ST_DeviceState :
STRUCT
xValid : BOOL := FALSE;
END_STRUCT
END_TYPE
- Related:
ST_DeviceStateTable
TYPE ST_DeviceStateTable :
STRUCT
iDeviceID:DINT;
iStateID:DINT;
rTargetPosition: REAL;
rTolerance: REAL;
rTransmission: REAL;
iBeamClass : DINT;
iPulseRate: DINT;
rPerPulseEnergy:REAL;
rUpper_eV: REAL;
rLower_eV: REAL;
rPhotonEnergy: REAL;
END_STRUCT
END_TYPE
ST_FF
TYPE ST_FF :
STRUCT
{attribute 'pytmc' := '
pv: Info
'}
Info : ST_FFInfo;
{attribute 'pytmc' := '
pv: Ovrd
'}
Ovrd : ST_FFOverride;
{attribute 'pytmc' := '
pv: OK
io: i
'}
OK : BOOL; // Fault logic state
FaultAck : BOOL; // Set when faulted, reset by logger.
ClearAck : BOOL;
{attribute 'pytmc' := '
pv: BeamPermitted
io: i
'}
BeamPermitted : BOOL; // Result of reset, veto, and fault logic, true beam off boolean
{attribute 'pytmc' := '
pv: Reset
io: o
'}
Reset : BOOL;
bsFF : RS;
rtReset : R_TRIG;
ftCountFault : F_TRIG;
END_STRUCT
END_TYPE
- Related:
ST_FFInfo
// These elements should be set at init and never changed.
TYPE ST_FFInfo :
STRUCT
{attribute 'pytmc' := '
pv: Path
io: i
'}
sPath : T_MaxString; // Full PLC path to FF object
{attribute 'pytmc' := '
pv: Desc
io: i
'}
Desc : T_MaxString; // Set at instantiation to a helpful description of the fast fault purpose
{attribute 'pytmc' := '
pv: DevName
io: i
'}
DevName : T_MaxString; // Component name, used in diagnostic to help narrow down where beam faults are coming from
{attribute 'pytmc' := '
pv: TypeCode
io: i
'}
TypeCode : UINT; // Set at instantiation to fault class code
///////////////////////////////////////////
// Warning, this variable is effectively a VETO of the fast fault if set FALSE
// Do not alter the state of this variable unless you really know what you're doing.
///////////////////////////////////////////
{attribute 'pytmc' := '
pv: InUse
io: i
'}
InUse : BOOL := FALSE; // If this FF is currently in-use
///////////////////////////////////////////
AutoReset : BOOL; // Automatically clear fast fault (latching vs non-latching)
Vetoable : BOOL := TRUE; // Can this fast fault be masked by the veto device input?
{attribute 'pytmc' := '
pv: InfoString
io: i
'}
InfoString : STRING;
END_STRUCT
END_TYPE
ST_FFOverride
TYPE ST_FFOverride :
STRUCT
{attribute 'pytmc' := '
pv: Duration
io: o
'}
Duration : DINT; // DINT to be compatible with EPICS
{attribute 'pytmc' := '
pv: Expiration
io: o
'}
Expiration : DINT; // DINT to be compatible with EPICS
{attribute 'pytmc' := '
pv: StartDT
io: o
'}
StartDT : DINT; // DINT to be compatible with EPICS
{attribute 'pytmc' := '
pv: Activate
io: o
'}
Activate : BOOL;
{attribute 'pytmc' := '
pv: Deactivate
io: o
'}
Deactivate : BOOL;
{attribute 'pytmc' := '
pv: ElapsedTime
io: i
'}
ElapsedTime : DINT; // DINT to be compatible with EPICS
{attribute 'pytmc' := '
pv: RemainingTime
io: i
'}
RemainingTime : DINT; // DINT to be compatible with EPICS
{attribute 'pytmc' := '
pv: Active
io: i
'}
Active: BOOL;
Timer : TP;
OvrdActLogAck : BOOL;
OvrdExpLogAck : BOOL;
tOvrdActivate : R_TRIG;
tOvrdExpiring : F_TRIG;
END_STRUCT
END_TYPE
ST_PMPS_Aperture
TYPE ST_PMPS_Aperture EXTENDS ST_PMPS_Aperture_IO :
STRUCT
(*
{attribute 'pytmc' := 'pv: Width
io: i
field: EGU mm'}
Width : REAL; // distance between horizontal slits (x)
{attribute 'pytmc' := 'pv: Height
io: i
field: EGU mm'}
Height : REAL; // distance between vertical slits (y)
{attribute 'pytmc' := 'pv: OK
io: i'}
xOK : BOOL; // status of aperture, false if error or in motion
*)
END_STRUCT
END_TYPE
ST_PMPS_Attenuator
TYPE ST_PMPS_Attenuator EXTENDS ST_PMPS_Attenuator_IO :
STRUCT
(*
{attribute 'pytmc' := 'pv: Att
io: i
field: EGU %
'}
nTran : UINT;
{attribute 'pytmc' := 'pv: OK
io: i
'}
xAttOK : UINT; // true = no errors and attenuator is stable
*)
END_STRUCT
END_TYPE
ST_ReactiveParams
TYPE ST_ReactiveParams :
STRUCT
{attribute 'pytmc' := 'pv: TempSP
io: i
field: PREC 2;
field: EGU "C";
'}
nTempSP: REAL;
{attribute 'pytmc' := 'pv: PressSP
io: i
field: PREC 2;
field: EGU "TORR";
'}
nPressSP: REAL;
END_STRUCT
END_TYPE
T_HashTableEntry
TYPE T_HashTableEntry :
STRUCT
{attribute 'pytmc' := '
pv: Key
io: i
'}
key : DWORD := 0;
value : PVOID := 0;
END_STRUCT
END_TYPE
GVLs
Global_Version
{attribute 'TcGenerated'}
{attribute 'no-analysis'}
{attribute 'linkalways'}
// This function has been automatically generated from the project information.
VAR_GLOBAL CONSTANT
{attribute 'const_non_replaced'}
stLibVersion_PMPS : ST_LibVersion := (iMajor := 3, iMinor := 3, iBuild := 0, iRevision := 0, nFlags := 1, sVersion := '3.3.0');
END_VAR
PMPS_GVL
{attribute 'qualified_only'}
VAR_GLOBAL
{attribute 'pytmc' := '
pv: @(PREFIX)RequestedBP
io: i
archive: 1Hz monitor
'}
stRequestedBeamParameters : ST_BeamParams; //Summarized request for the line, as recognized by the line arbiter PLC
{attribute 'pytmc' := '
pv: @(PREFIX)CurrentBP
io: i
archive: 1Hz monitor
'}
stCurrentBeamParameters : ST_BeamParams; //Currently active BP set, broadcast by the line arbiter PLC
{attribute 'pytmc' := '
pv: @(PREFIX)eVRangeCnst
io: i
archive: 1Hz monitor
field: DESC Active eV Range constants
field: EGU eV
'}
g_areVBoundaries : ARRAY [0..g_cBoundaries] OF REAL;
PERange : PE_Ranges; //Included to place the ev ranges properly
END_VAR
VAR_GLOBAL RETAIN
// Statistics
{attribute 'pytmc' := '
pv: @(PREFIX)SuccessfulPreemptions
io: i
'}
SuccessfulPreemption : UDINT; // Any time BPTM applies a new BP request which is confirmed
{attribute 'pytmc' := '
pv: @(PREFIX)AccumulatedFastFaults
io: i
'}
AccumulatedFF : UDINT; // Any time a FF occurs
BP_jsonDoc : SJsonValue;
END_VAR
VAR_GLOBAL CONSTANT
EXCLUDED_ASSERTION_ID : UDINT := 16#FFFFFFFF; //An assertion ID that should always return "not found" in the assertion pool
VISIBLE_TEST_VELOCITY : LREAL := 10;
FAST_TEST_VELOCITY : LREAL := 100;
MAX_DEVICE_STATES : UDINT := 300;
TRANS_SCALING_FACTOR : REAL := REAL#1.0; // Scaling factor for fixed-point transmission
AUX_ATTENUATORS : UINT := 16; // Maximum # of attenuators in the PMPS
MAX_VETO_DEVICES : UINT := 16;
stAttenuators : ST_PMPS_Attenuator :=(nTran:=1,xAttOK:=1);
{attribute 'pytmc' := '
pv: @(PREFIX)FullBeamCnst
io: i
archive: 1Hz monitor
field: DESC Full beam constant
'}
cstFullBeam : ST_BeamParams := (
nTran := 1,
neVRange := 2#1111_1111_1111_1111_1111_1111_1111_1111,
nRate := UDINT#1000000,
nBCRange := 2#0111_1111_1111_1111,
astApertures := [(Width:=1E3, Height:=1E3), (Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3)],
astAttenuators := [PMPS_GVL.AUX_ATTENUATORS(stAttenuators)]
);
{attribute 'pytmc' := '
pv: @(PREFIX)0RateBeamCnst
io: i
archive: 1Hz monitor
field: DESC 0-rate beam constant
'}
cst0RateBeam : ST_BeamParams := ( // Use for transition requests
nTran := 1,
neVRange := 2#1111_1111_1111_1111_1111_1111_1111_1111,
nRate := 0,
nBCRange := 2#0000_0000_0000_0000,
astApertures := [(Width:=1E3, Height:=1E3), (Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3),(Width:=1E3, Height:=1E3)],
astAttenuators := [PMPS_GVL.AUX_ATTENUATORS(stAttenuators)]
);
(* {attribute 'pytmc' := '
pv: @(PREFIX)SafeBeamCnst
io: i
archive: 1Hz monitor
field: DESC Safe beam constant
'}
cstSafeBeam : ST_BeamParams := (
nTran := 0,
neVRange := 0,
nRate := 0
);*)
cnMaxStateArrayLen : INT := 20;
MAX_APERTURES : UINT := 4; // Maximum # of power slits in the PMPS
{warning disable C0228}
DUMMY_AUX_ATT_ARRAY : ARRAY [1..PMPS_GVL.AUX_ATTENUATORS] OF ST_PMPS_Attenuator;
{warning restore C0228}
g_cBoundaries : INT := 31;
//////////////////////////
// L Undulator constants
//////////////////////////
///////////////////////////////////////
{attribute 'pytmc' := '
pv: @(PREFIX)eVRangeHyst
io: i
archive: 1Hz monitor
field: DESC eV Range hystersis
field: EGU eV
'}
reVHyst: REAL := 5; //
{attribute 'pytmc' := '
pv: @(PREFIX)L:eVRangeCnst
io: i
archive: 1Hz monitor
field: DESC eV Range constants
field: EGU eV
'}
g_areVBoundariesL : ARRAY [0..g_cBoundaries] OF REAL := [
1.0E3,
1.7E3,
2.1E3,
2.5E3,
3.8E3,
4.0E3,
5.0E3,
7.0E3,
7.5E3,
7.7E3,
8.9E3,
10.0E3,
11.1E3,
12.0E3,
13.0E3,
13.5E3,
14.0E3,
16.9E3,
18.0E3,
20.0E3,
22.0E3,
24.0E3,
25.0E3,
25.5E3,
26.0E3,
27.0E3,
28.0E3,
28.5E3,
29.0E3,
30.0E3,
60.0E3,
90.0E3
];
{attribute 'pytmc' := '
pv: @(PREFIX)K:eVRangeCnst
io: i
archive: 1Hz monitor
field: DESC eV Range constants
field: EGU eV
'}
g_areVBoundariesK : ARRAY [0..g_cBoundaries] OF REAL := [
100,
250,
270,
350,
400,
450,
480,
530,
680,
730,
850,
1.10E3,
1.15E3,
1.25E3,
1.45E3,
1.50E3,
1.55E3,
1.65E3,
1.70E3,
1.75E3,
1.82E3,
1.85E3,
2.00E3,
2.20E3,
2.50E3,
2.80E3,
3.00E3,
3.15E3,
3.50E3,
4.00E3,
5.30E3,
7.00E3
];
END_VAR
- Related:
PMPS_PARAM
{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
MAX_FAST_FAULTS : UINT := 50; // Max fast faults for an FFO
MAX_ASSERTIONS : UDINT := 20; //Maximum number of BP requests in the arbiter
TRANS_MARGIN : REAL := REAL#0.05; // Allowed % margin above requested transmission level in SafeBPCompare (0.0500 = 5deci% default). Note: change this value if scaling factor changes.
END_VAR
PMPS_TOOLS
{attribute 'qualified_only'}
VAR_GLOBAL
fbJson : FB_JsonSaxWriter;
END_VAR
POUs
APT_TO_IO
FUNCTION APT_TO_IO : ST_PMPS_Aperture_IO
VAR_INPUT
Apt : ST_PMPS_Aperture;
END_VAR
VAR
END_VAR
APT_TO_IO.Height := Apt.Height;
APT_TO_IO.Width := Apt.Width;
APT_TO_IO.xOK := Apt.xOK;
END_FUNCTION
- Related:
ATT_TO_IO
FUNCTION ATT_TO_IO : ST_PMPS_Attenuator_IO
VAR_INPUT
Att : ST_PMPS_Attenuator;
END_VAR
VAR
END_VAR
ATT_TO_IO.nTran := Att.nTran;
ATT_TO_IO.xAttOK := Att.xAttOK;
END_FUNCTION
- Related:
BeamParameterTransitionManager
(*
Implements the procedure for safely transitioning between device states.
NOTE:
The BPTM will throw an error if the arbiter does not have enough space for the transition and new final assertion.
*)
{attribute 'no_check'}
FUNCTION_BLOCK BeamParameterTransitionManager
VAR_IN_OUT
fbArbiter : FB_Arbiter; //Connect to local arbiter
END_VAR
VAR_INPUT
i_sDeviceName : STRING:='Device'; // Name of the device requesting the transition
i_TransitionAssertionID : UDINT := 0; // Must not be 0 or EXCLUDED_ID
i_stTransitionAssertion : ST_BeamParams := PMPS_GVL.cst0RateBeam; //Assertion required during transition (always safer than anything inbetween)
i_nRequestedAssertionID : UDINT := 0; // Must not be 0 or EXCLUDED_ID
i_stRequestedAssertion : ST_BeamParams := ( nTran := 0, neVRange := 0, nRate := 0, nBCRange :=0);// PMPS_GVL.cstSafeBeam; //Requested assertion, change whenever
i_xMoving : BOOL := FALSE; //Provide rising edge when device begins moving <remove>
i_xDoneMoving : BOOL := FALSE; //Provide rising edge when device is done with a move
stCurrentBeamParameters : ST_BeamParams := PMPS_GVL.cstFullBeam; //Connect to current beam parameters
// Rising edge to cycle back through the BPTM process. Use if something in the process timed out or failed. This will interrupt a current process
bRetry : BOOL := FALSE;
END_VAR
VAR_OUTPUT
q_xTransitionAuthorized : BOOL := FALSE; //Rising edge indicating the device is safe to move, use as input to move execute (which requires a rising edge)
bError : BOOL; // Set if some issue occurs within the bptm
nErrId : UINT; // Set to non-zero to help understand the error.
bDone : BOOL;
bBusy : BOOL;
END_VAR
VAR
nTargetAssertionID : UDINT := 0;
stTargetAssertion : ST_BeamParams; // Target assertion
nCurrentAssertionID : UDINT := 0; // ID of last set state (zero until a state is reached)
xNewBP : BOOL;
xTranBP : BOOL;
xFinalBPInArb : BOOL;
xFinalBP : BOOL;
eBPTMState : E_BPTMState := Init;
ePrevState : E_BPTMState := Init;
xEntry : BOOL;
rTransition : R_TRIG;
xNewTarget : BOOL;
bTransAssertionFailed : BOOL;
bFinalAssertionFailed : BOOL;
LogStrBuffer : ARRAY [0..LogBuffSize] OF STRING;
LogBuffIdx : FB_Index := (LowerLimit:=0, UpperLimit:=LogBuffSize);
nAssrtAttempt : INT; // Number of times we have tried asserting a BP set
rtRetry : R_TRIG;
rtError : R_TRIG;
ffTimeout : FB_FastFault := (
i_Desc := 'Preemptive requests timed out in BPTM',
i_TypeCode := 16#A,
i_xAutoReset := FALSE);
rtDoneMoving : R_TRIG;
bLatchDoneMoving : BOOL;
bFirstMove : BOOL := TRUE;
END_VAR
VAR CONSTANT
LogBuffSize : INT := 40;
cMaxAttempts : INT := 3;
(* The thought here is, a BPTM needs at most 2 arbiter slots to complete a transition.
If we're at capacity, it means some BPTM before this one has begun a transition,
and will require at least one more arbiter spot to complete.
*)
cReqArbCapacity : UDINT := 2;
END_VAR
// Logic for retry button
rtRetry(CLK:=bRetry);
bRetry := FALSE;
// Logic for catching Move Done rising edge
rtDoneMoving(CLK:=i_xDoneMoving);
bLatchDoneMoving S= rtDoneMoving.Q;
// Logic for interrupting BPTM and changing requests
IF nTargetAssertionID <> i_nRequestedAssertionID OR rtRetry.Q THEN
bError := FALSE;
nErrID := 0;
bDone := FALSE;
bBusy := TRUE;
// Execute exit actions before transition back to NewTarget
CASE eBPTMState OF
Transitioning:
DeauthorizeTransition();
//CleaningUp:
END_CASE
eBPTMState := E_BPTMState.NewTarget;
xEntry := eBPTMState <> ePrevState;
END_IF
IF xEntry THEN nAssrtAttempt := 0; END_IF
rtError(CLK:=bError);
IF rtError.Q THEN
eBPTMState := E_BPTMState.Error;
END_IF
// States
CASE eBPTMState OF
NewTarget:
IF xEntry THEN
NewTarget_ENTRY();
ELSIF xNewBP THEN
eBPTMState := E_BPTMState.RequestBP;
ELSE
SetNewTarget();
END_IF
RequestBP:
IF PMPS_PARAM.MAX_ASSERTIONS - fbArbiter.nEntryCount >= cReqArbCapacity THEN
AssertTransitionBP();
AssertFinalBP();
eBPTMState := E_BPTMState.WaitForBP;
ELSE
LogActions('Arbiter at capacity, leaving space for other BPTM to finish.');
nErrId := PMPS_CODES.NoRoomInArb;
bError := TRUE;
END_IF
WaitForBP:
WaitingForTransitionAssertion_DO();
WaitingForFinalAssertion_DO();
IF xTranBP AND xFinalBPInArb THEN eBPTMState := E_BPTMState.Transitioning; END_IF
Transitioning:
IF xEntry THEN
AuthorizeTransition();
ELSIF bLatchDoneMoving OR (bFirstMove AND i_xDoneMoving) THEN
DeauthorizeTransition();
eBPTMState := E_BPTMState.WaitForFinalBP;
END_IF
WaitForFinalBP:
xFinalBP := F_SafeBPCompare(stCurrentBeamParameters, stTargetAssertion);
IF xFinalBP THEN
eBPTMState := E_BPTMState.CleaningUp;
END_IF
CleaningUp:
IF xEntry THEN
RemoveTransitionAssertion();
ELSE
eBPTMState := E_BPTMState.Done;
END_IF
Done:
bDone := TRUE;
bFirstMove := FALSE;
LogActions('Returning to idle');
eBPTMState := E_BPTMState.Idle;
Error:
bError := TRUE;
eBPTMState := E_BPTMState.Idle;
END_CASE
xEntry := ePrevState <> eBPTMState;
ePrevState := eBPTMState;
bDone R= bError;
bBusy R= bError OR bDone;
END_FUNCTION_BLOCK
ACTION AssertFinalBP:
//q_stFinalAssertion := stTargetAssertion;
//Clearing previous target parameters
(* This is considered safe at this step as the transition
state beam parameters should always be safer than the
current or target state beam parameters
*)
IF nCurrentAssertionID <> 0 THEN
LogActions(CONCAT('Removing previous request: ', DWORD_TO_HEXSTR(nCurrentAssertionID, 4, false)) );
fbArbiter.RemoveRequest( nCurrentAssertionID );
END_IF
LogActions(CONCAT('Asserting target req.: ', DWORD_TO_HEXSTR(nTargetAssertionID, 4, false)) );
//Asserting target parameters
bFinalAssertionFailed := NOT fbArbiter.AddRequest(
nTargetAssertionID,
stTargetAssertion,
i_sDeviceName
);
IF bFinalAssertionFailed THEN
LogActions('Assert final failed. ');
nAssrtAttempt := nAssrtAttempt + 1;
IF nAssrtAttempt > cMaxAttempts THEN
bError := TRUE;
nErrID := PMPS_CODES.FinalAssrtFail;
END_IF
ELSE
// Remembering final assertionID for removal later
nCurrentAssertionID := nTargetAssertionID;
END_IF
END_ACTION
ACTION AssertTransitionBP:
LogActions( CONCAT('Removing transition id: ', DWORD_TO_STRING(i_TransitionAssertionID)) );
fbArbiter.RemoveRequest(i_TransitionAssertionID);
LogActions( CONCAT('Requesting transition id: ', DWORD_TO_STRING(i_TransitionAssertionID)) );
bTransAssertionFailed := NOT fbArbiter.AddRequest(
i_TransitionAssertionID,
i_stTransitionAssertion,
i_sDeviceName
);
IF bTransAssertionFailed THEN
LogActions('Assert trans. failed. ');
nAssrtAttempt := nAssrtAttempt + 1;
IF nAssrtAttempt > cMaxAttempts THEN
bError := TRUE;
nErrID := PMPS_CODES.TransAssrtFail;
END_IF
END_IF
END_ACTION
ACTION AuthorizeTransition:
q_xTransitionAuthorized := TRUE;
bLatchDoneMoving R= q_xTransitionAuthorized;
LogActions('Authorizing transition');
END_ACTION
ACTION DeauthorizeTransition:
q_xTransitionAuthorized := FALSE;
LogActions('Deauthorizing transition');
END_ACTION
ACTION NewTarget_ENTRY:
xNewBP := False;
END_ACTION
ACTION RemoveTransitionAssertion:
//q_stTransitionAssertion := PMPS_GVL.cstFullBeam;
//Removing transition assertion
fbArbiter.RemoveRequest( i_TransitionAssertionID );
LogActions('Removing transition req');
//TODO add something to verify removal
END_ACTION
ACTION RequestBP_DO:
// Request BP atomically so we know we'll finish the BPTM cycle
IF fbArbiter.nEntryCount < cReqArbCapacity THEN
LogActions( CONCAT('Removing transition id: ', DWORD_TO_STRING(i_TransitionAssertionID)) );
fbArbiter.RemoveRequest(i_TransitionAssertionID);
LogActions( CONCAT('Requesting transition id: ', DWORD_TO_STRING(i_TransitionAssertionID)) );
bTransAssertionFailed := NOT fbArbiter.AddRequest(
i_TransitionAssertionID,
i_stTransitionAssertion,
i_sDeviceName
);
IF bTransAssertionFailed THEN
LogActions('Assert trans. failed. ');
nAssrtAttempt := nAssrtAttempt + 1;
IF nAssrtAttempt > cMaxAttempts THEN
bError := TRUE;
nErrID := PMPS_CODES.TransAssrtFail;
END_IF
END_IF
ELSE
LogActions('Arbiter at capacity, leaving space for other BPTM to finish.');
END_IF
END_ACTION
ACTION SetNewTarget:
IF F_ValidReqID(i_nRequestedAssertionID) AND
F_ValidReqID(i_TransitionAssertionID) THEN
stTargetAssertion := i_stRequestedAssertion;
nTargetAssertionID := i_nRequestedAssertionID;
LogActions('New target set');
xNewBP := TRUE;
ELSE
IF NOT F_ValidReqID(i_nRequestedAssertionID) THEN
nErrID := PMPS_CODES.BadTargetID;
ELSIF NOT F_ValidReqID(i_TransitionAssertionID) THEN
nErrID := PMPS_CODES.BadTransID;
END_IF
LogActions('Error in set new target');
bError := TRUE;
END_IF
END_ACTION
ACTION WaitingForFinalAssertion_DO:
//Final Assertion Verification
xFinalBPInArb := fbArbiter.CheckRequest( i_nRequestedAssertionID);;
END_ACTION
ACTION WaitingForFinalAssertion_EXIT:
//Set this bool false so we can get a rising edge on the next try
xFinalBP := FALSE;
END_ACTION
ACTION WaitingForTransitionAssertion_DO:
//Transition Assertion Verification
xTranBP := F_SafeBPCompare0Rate(stCurrentBeamParameters, i_stTransitionAssertion) AND
fbArbiter.CheckRequest(i_TransitionAssertionID);
END_ACTION
ACTION WaitingForTransitionAssertion_EXIT:
xTranBP := FALSE;
END_ACTION
{attribute 'no_check'}
METHOD LogActions : BOOL
VAR_INPUT
LogStr : STRING;
END_VAR
LogStrBuffer[LogBuffIdx.IncVal()] := LogStr;
END_METHOD
BP_TO_IO
{attribute 'no_check'}
FUNCTION BP_TO_IO : ST_BeamParams_IO
VAR_INPUT
BP : ST_BeamParams;
END_VAR
VAR
idx : UINT := 0;
END_VAR
FOR idx := 1 TO PMPS_GVL.AUX_ATTENUATORS DO
BP_TO_IO.astAttenuators[idx] := ATT_TO_IO(BP.astAttenuators[idx]);
END_FOR
FOR idx := 1 TO PMPS_GVL.MAX_APERTURES DO
BP_TO_IO.astApertures[idx] := APT_TO_IO(BP.astApertures[idx]);
END_FOR
BP_TO_IO.aVetoDevices := BP.aVetoDevices;
BP_TO_IO.nTran := BP.nTran;
BP_TO_IO.nCohortInt := ULINT_TO_UDINT(BP.nCohortInt);
BP_TO_IO.neVRange := BP.neVRange;
BP_TO_IO.neV := BP.neV;
BP_TO_IO.nBCRange := BP.nBCRange;
BP_TO_IO.nBeamClass := BP.nBeamClass;
BP_TO_IO.nMachineMode := BP.nMachineMode;
BP_TO_IO.nRate := BP.nRate;
BP_TO_IO.xValid := BP.xValid;
BP_TO_IO.xValidToggle := BP.xValidToggle;
END_FUNCTION
- Related:
CheckBounds
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckBounds : DINT
VAR_INPUT
index, lower, upper: DINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF index < lower THEN
CheckBounds := lower;
ELSIF index > upper THEN
CheckBounds := upper;
ELSE
CheckBounds := index;
END_IF
{flow}
END_FUNCTION
CheckDivDInt
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivDInt : DINT
VAR_INPUT
divisor:DINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
CheckDivDInt:=1;
ELSE
CheckDivDInt:=divisor;
END_IF;
{flow}
END_FUNCTION
CheckDivLInt
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivLInt : LINT
VAR_INPUT
divisor:LINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
CheckDivLInt:=1;
ELSE
CheckDivLInt:=divisor;
END_IF;
{flow}
END_FUNCTION
CheckDivLReal
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivLReal : LREAL
VAR_INPUT
divisor:LREAL;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
CheckDivLReal:=1;
ELSE
CheckDivLReal:=divisor;
END_IF;
{flow}
END_FUNCTION
CheckDivReal
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckDivReal : REAL
VAR_INPUT
divisor:REAL;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF divisor = 0 THEN
CheckDivReal:=1;
ELSE
CheckDivReal:=divisor;
END_IF;
{flow}
END_FUNCTION
CheckLRangeSigned
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckLRangeSigned : LINT
VAR_INPUT
value, lower, upper: LINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF (value < lower) THEN
CheckLRangeSigned := lower;
ELSIF(value > upper) THEN
CheckLRangeSigned := upper;
ELSE
CheckLRangeSigned := value;
END_IF
{flow}
END_FUNCTION
CheckLRangeUnsigned
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckLRangeUnsigned : ULINT
VAR_INPUT
value, lower, upper: ULINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF (value < lower) THEN
CheckLRangeUnsigned := lower;
ELSIF(value > upper) THEN
CheckLRangeUnsigned := upper;
ELSE
CheckLRangeUnsigned := value;
END_IF
{flow}
END_FUNCTION
CheckPointer
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckPointer : POINTER TO BYTE
VAR_INPUT
ptToTest : POINTER TO BYTE; // Destination address of the pointer
iSize : DINT; // Size of the type the pointer points to. (e.g: 20 for POINTER TO ARRAY [0..9] OF INT)
iGran : DINT; // Granularity of the pointer access. This is the size of the biggest non-structured data type in the type the pointer points to. (e.g: 2 for POINTER TO ARRAY [0..9] OF INT)
bWrite: BOOL; // Indicates read or write Access. TRUE = write access.
END_VAR
// No standard way of implementation. Fill your own code here
CheckPointer := ptToTest;
{flow}
END_FUNCTION
CheckRangeSigned
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckRangeSigned : DINT
VAR_INPUT
value, lower, upper: DINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF (value < lower) THEN
CheckRangeSigned := lower;
ELSIF(value > upper) THEN
CheckRangeSigned := upper;
ELSE
CheckRangeSigned := value;
END_IF
{flow}
END_FUNCTION
CheckRangeUnsigned
// Implicitly generated code : DO NOT EDIT
FUNCTION CheckRangeUnsigned : UDINT
VAR_INPUT
value, lower, upper: UDINT;
END_VAR
// Implicitly generated code : Only an Implementation suggestion
{noflow}
IF (value < lower) THEN
CheckRangeUnsigned := lower;
ELSIF(value > upper) THEN
CheckRangeUnsigned := upper;
ELSE
CheckRangeUnsigned := value;
END_IF
{flow}
END_FUNCTION
F_AttenuatorOK
(*
Indicates if the attenuator can be considered safe for a device,
given its status word.
In local mode, the atteunator ignores PMPS requests, following a local
setpoint. In this mode, a device cannot trust that the attenuator will not
increase transmission beyond its own setpoint. Therefore any
indication of a changing transmission should cause a fault.
In PMPS mode, if a device has submitted a preemptive request and received
acknowledgement, then the transmission setpoint will be governed at least
by that device's setpoint. Therefore, the attenuator may be moving without
generating a fault.
*)
FUNCTION F_AttenuatorOK : BOOL
VAR_INPUT
xStatus : WORD;
END_VAR
VAR
END_VAR
IF xStatus.2 THEN // Local mode
F_AttenuatorOK := xStatus.0 and NOT xStatus.1;
ELSIF xStatus.3 AND NOT xStatus.2 THEN // PMPS mode
F_AttenuatorOK := TRUE;
END_IF
F_AttenuatorOK := xStatus.4 AND F_AttenuatorOK;
END_FUNCTION
F_BPWithID
FUNCTION F_BPWithID : ST_BP_ArbInternal
VAR_INPUT
BP : REFERENCE TO ST_BeamParams;
ID : DWORD;
END_VAR
VAR
BpInt : ST_BP_ArbInternal;
END_VAR
MEMCPY(ADR(F_BPWithID), ADR(BP), SIZEOF(BP));
F_BPWithID.nId := ID;
END_FUNCTION
- Related:
F_CalculatePhotonEnergy
FUNCTION F_CalculatePhotonEnergy : LREAL
VAR_INPUT
(* Electron energy in GeV *)
fElectronEnergy_GeV : LREAL;
(* Undulator period in mm *)
fUndulatorPeriod_mm : LREAL;
(* Unitless undulator K parameter / strength *)
fUndulatorStrength : LREAL;
END_VAR
VAR
fDenominator: LREAL;
END_VAR
VAR CONSTANT
(* GeV [electron rest mass/energy] *)
m_e : LREAL := 0.0005109989461;
(* Js [Planck's constant] *)
h : LREAL := 6.62607004E-34;
(* C [electron charge] *)
e : LREAL := 1.6021766208E-19;
(* m/s, speed of light *)
c : LREAL := 299792458;
END_VAR
(*
Reference Python implementation
def calculate_photon_energy(electron_energy, period, k):
'''
Calculate photon energy, in eV.
Parameters
----------
electron_energy : float
Electron energy in GeV
period : float
Undulator period, in mm
k : float
Undulator K parameter (strength), unitless
'''
m_e = 0.0005109989461 # GeV [electron rest mass/energy]
h = 6.62607004e-34 # Js [Planck's constant]
e = 1.6021766208e-19 # C [electron charge]
c = 299792458 # m/s, speed of light
return ((2. * (electron_energy / m_e) ** 2 * h * c) /
(e * period * 1e-3 * (1 + k ** 2 / 2.)))
*)
fDenominator := (e * fUndulatorPeriod_mm * 1E-3 * (1.0 + EXPT(fUndulatorStrength, 2.0) / 2.0));
F_CalculatePhotonEnergy := (2.0 * EXPT(fElectronEnergy_GeV / m_e, 2.0) * h * c) / MAX(fDenominator, 4.94065645841247E-324);
END_FUNCTION
F_DeviceState_To_DeviceStateExt
FUNCTION F_DeviceState_To_DeviceStateExt : ST_DeviceStateExt
VAR_INPUT
stDeviceState : ST_DeviceState;
xValid : BOOL;
END_VAR
VAR
END_VAR
F_DeviceState_To_DeviceStateExt.nStateRef := stDeviceState.nStateRef;
F_DeviceState_To_DeviceStateExt.rPosition := stDeviceState.rPosition;
F_DeviceState_To_DeviceStateExt.rTolerance := stDeviceState.rTolerance;
F_DeviceState_To_DeviceStateExt.sStateName := stDeviceState.sStateName;
F_DeviceState_To_DeviceStateExt.stReqBeamParam := stDeviceState.stReqBeamParam;
F_DeviceState_To_DeviceStateExt.xValid := xValid;
END_FUNCTION
- Related:
F_DifferentBeamParams
(*Compares BeamParam1 to BeamParam2, if any parameters of BeamParam1 are different than BeamParam2
the result will be true. *)
{attribute 'no_check'}
FUNCTION F_DifferentBeamParams : BOOL
VAR_INPUT
BeamParam1 : ST_BeamParams;
BeamParam2 : ST_BeamParams;
END_VAR
VAR
xAttOK: BOOL := FALSE;
xPPmjOK: BOOL := FALSE;
xEvOK: BOOL := FALSE;
xRateOK: BOOL := FALSE;
xaStopper : BOOL := FALSE;
xaAtt : BOOL := FALSE;
xaApt : BOOL := FALSE;
xBCOK : BOOL := FALSE;
idx : UINT;
END_VAR
xAttOK := BeamParam1.nTran <> BeamParam2.nTran;
xEvOK := BeamParam1.neVRange <> BeamParam2.neVRange;
xRateOK := BeamParam1.nRate <> BeamParam2.nRate;
xBCOK := BeamParam1.nBCRange <> BeamParam2.nBCRange;// OR BeamParam1.nBeamClass <> BeamParam2.nBeamClass ; //Is this right?
//ast Attenuators
FOR idx:=1 TO PMPS_GVL.AUX_ATTENUATORS DO
xaAtt S= (BeamParam1.astAttenuators[idx].nTran <> BeamParam2.astAttenuators[idx].nTran);
END_FOR
// Stoppers
FOR idx:=1 TO PMPS_GVL.MAX_VETO_DEVICES DO
xaStopper S= (BeamParam1.aVetoDevices[idx] <> BeamParam2.aVetoDevices[idx]);
END_FOR
// ast Apertures
FOR idx:=1 TO PMPS_GVL.MAX_APERTURES DO
xaApt S= (BeamParam1.astApertures[idx].Height <> BeamParam2.astApertures[idx].Height) OR
(BeamParam1.astApertures[idx].Width <> BeamParam2.astApertures[idx].Width);
END_FOR
F_DifferentBeamParams := xAttOK OR xPPmjOK OR xEvOK OR xRateOK OR xaStopper OR xaAtt OR xaApt OR xBCOK;
END_FUNCTION
- Related:
F_eVExcludeRange
FUNCTION F_eVExcludeRange : DWORD
(*
Given an lower and upper end of an exclusion range, return the corresponding eV bitmask.
eVs between fLower and fUpper will be considered unsafe, and eVs outside of this range will be
considered safe, with the exception of the endpoints and values near the endpoints if they land
far from an eV boundary.
Call this in your init cycle to set up your eV bitmasks for more readable code
that is also more resiliant to eV range definition adjustments.
*)
VAR_INPUT
fLower: REAL;
fUpper: REAL;
END_VAR
F_eVExcludeRange := F_eVIncludeRange(0, fLower) OR F_eVIncludeRange(fUpper, PMPS_GVL.g_areVBoundaries[PMPS_GVL.g_cBoundaries]);
END_FUNCTION
- Related:
F_eVIncludeRange
FUNCTION F_eVIncludeRange : DWORD
(*
Given an lower and upper end of an inclusion range, return the corresponding eV bitmask.
eVs between fLower and fUpper will be considered safe, with the exception of the endpoints and values
near the endpoints if they land far from an eV boundary.
Call this in your init cycle to set up your eV bitmasks for more readable code
that is also more resiliant to eV range definition adjustments.
*)
VAR_INPUT
fLower: REAL;
fUpper: REAL;
END_VAR
VAR
nBitmask: DWORD := 0;
nIndex: INT;
nBit: USINT := 0;
fPrev: REAL := 0;
END_VAR
FOR nIndex := 0 TO PMPS_GVL.g_cBoundaries DO
IF fLower <= fPrev AND fUpper >= PMPS_GVL.g_areVBoundaries[nIndex] THEN
nBitmask := nBitmask + SHL(1, nBit);
END_IF
fPrev := PMPS_GVL.g_areVBoundaries[nIndex];
nBit := nBit + 1;
END_FOR
F_eVIncludeRange := nBitmask;
END_FUNCTION
- Related:
F_eVRangeCalculator
(*
A. Wallace 2019-8-8
Provides bit range of eV.
LastWord = 0 (Result)
----------------------------------
reV = 300 = (0000_0000_0000_0010)
reV = 301 = (0000_0000_0000_0100)
LastWord = 0000_0000_0000_0010 (Result)
----------------------------------
reV = 300 + <rHyst = (0000_0000_0000_0110)
reV = 300 + >rHyst = (0000_0000_0000_0100)
*)
{attribute 'no_check'}
FUNCTION F_eVRangeCalculator : DWORD // Bit-range of current photon energy
VAR_INPUT
reV : REAL; // Photon energy (keV)
LastWord : DWORD; // Range word of previous cycle
END_VAR
VAR
nIndex: INT;
RangeWord: DWORD;
rPreviousBoundary: REAL := 0;
MaxEv : REAL := PMPS_GVL.g_areVBoundaries[PMPS_GVL.g_cBoundaries];
Boundaries : ARRAY [0..PMPS_GVL.g_cBoundaries] OF REAL;
END_VAR
{IF defined (L)}
Boundaries := PMPS_GVL.g_areVBoundariesL;
{ELSIF defined (K)}
Boundaries := PMPS_GVL.g_areVBoundariesK;
{END_IF}
MaxEv := Boundaries[PMPS_GVL.g_cBoundaries];
IF reV <= 0 OR reV > MaxEv THEN F_eVRangeCalculator := 16#FFFF_FFFF; RETURN; END_IF // Failsafe
FOR nIndex := 0 TO PMPS_GVL.g_cBoundaries DO
// If previously active then check if it should be cleared
IF LastWord.0 THEN
// Hysteresis condition
IF (reV > MIN(Boundaries[nIndex] + PMPS_GVL.reVHyst, MaxEv) ) OR
(reV < MAX(rPreviousBoundary - PMPS_GVL.reVHyst, 0) ) THEN
RangeWord.0 := 0; // If out of range of hys. mark inactive
ELSE
RangeWord.0 := 1;
END_IF
ELSE // Check if we should mark as active
IF (reV <= Boundaries[nIndex]) AND
(reV > rPreviousBoundary) OR
(nIndex = 0 AND reV = 0 ) THEN
RangeWord.0 := 1;
END_IF
END_IF
// Catch if the hysteresis is set too large for the range
IF rPreviousBoundary + PMPS_GVL.reVHyst >= Boundaries[nIndex] THEN
RangeWord.0 := 1; // Setting the bit true here as a failsafe
//<TODO> Add major error or warning here
END_IF
rPreviousBoundary := Boundaries[nIndex];
// Shift to next bit
RangeWord := ROR(RangeWord, 1);
LastWord := SHR(LastWord, 1);
END_FOR
F_eVRangeCalculator := RangeWord;
END_FUNCTION
- Related:
F_eVWithinSpec
//reV must be within permitted range.
{attribute 'no_check'}
FUNCTION F_eVWithinSpec : BOOL
VAR_INPUT
reV : REAL; //Photon energy to check if within permitted range
nPermittedRange : DWORD; //Permitted range
END_VAR
VAR
//Holding register for permitted ranges
nPermittedRangeHolding : DWORD;
//For loop counter
nIndex : INT;
// Lower boundary for range check
rPreviousBoundary: REAL := 0;
Boundaries: ARRAY [0..PMPS_GVL.g_cBoundaries] OF REAL;
END_VAR
(* How this works:
The within range bool is initialized to false.
Load the word representation of ranges into a holding register.
If the bit at position zero of the holding register is true, and the input eV is
below the boundary at the index position of the boundary array, and the previous boundary
(which initializes at 0), then the within range bool is set.
Then, we stash the upper boundary as the lower boundary for the next comparison,
and shift the holding register right, which moves the next bit to the zero position.
*)
{IF defined (L)}
Boundaries := PMPS_GVL.g_areVBoundariesL;
{ELSIF defined (K)}
Boundaries := PMPS_GVL.g_areVBoundariesK;
{END_IF}
nPermittedRangeHolding := nPermittedRange;
FOR nIndex := 0 TO PMPS_GVL.g_cBoundaries DO
F_eVWithinSpec S= nPermittedRangeHolding.0 AND(
(reV <= Boundaries[nIndex]) AND
(reV >= rPreviousBoundary) );
IF F_eVWithinSpec THEN RETURN; END_IF
rPreviousBoundary := Boundaries[nIndex];
nPermittedRangeHolding := SHR(nPermittedRangeHolding, 1);
END_FOR
END_FUNCTION
- Related:
F_InitTestBP
// Establishes a BP set for testing.
// Specifically this means a BP that has all the status bits set to an OK state
FUNCTION F_InitTestBP : ST_BeamParams
VAR_INPUT
BP : ST_BeamParams;
END_VAR
VAR
END_VAR
END_FUNCTION
- Related:
F_PMPS_JSON
FUNCTION F_PMPS_JSON : STRING
VAR_INPUT
sDevName : STRING := '';
sPath : STRING := '';
nTypeCode : UINT := 0;
END_VAR
VAR
END_VAR
PMPS_TOOLS.fbJson.StartObject();
PMPS_TOOLS.fbJson.AddKey('pmps_typecode');
PMPS_TOOLS.fbJson.AddUdint(nTypeCode);
PMPS_TOOLS.fbJson.AddKey('pmps_path');
PMPS_TOOLS.fbJson.AddString(sPath);
PMPS_TOOLS.fbJson.AddKey('pmps_device_name');
PMPS_TOOLS.fbJson.AddString(sDevName);
PMPS_TOOLS.fbJson.EndObject();
F_PMPS_JSON := PMPS_TOOLS.fbJson.GetDocument();
PMPS_TOOLS.fbJson.ResetDocument();
END_FUNCTION
- Related:
F_SafeBPCompare
(*Compares BeamParam1 to BeamParam2: if the parameters of BeamParam1 are more conservative than BeamParam2
the result will be true. *)
// Does not consider status. Status must be evaluated elsewhere.
{attribute 'no_check'}
FUNCTION F_SafeBPCompare : BOOL
VAR_INPUT
BeamParam1 : ST_BeamParams; //Must be more conservative than 2
BeamParam2 : ST_BeamParams;
END_VAR
VAR
// Internal Attenuation OK boolean
xAttOK: BOOL := FALSE;
// Internal Per-pulse energy OK boolean
xPPmjOK: BOOL := FALSE;
// Internal photon energy OK boolean
xEvOK: BOOL := FALSE;
// Internal Beam Rate OK boolean
xRateOK: BOOL;
//Internal Beam Class Range OK boolean
xBCOK : BOOL := FALSE;
idx : UINT;
xAuxAttOK : BOOL := TRUE;
xAuxAprtOK : BOOL := TRUE;
Att1 : REFERENCE TO ST_PMPS_Attenuator;
Att2 : REFERENCE TO ST_PMPS_Attenuator;
END_VAR
xAttOK := BeamParam1.nTran <= MIN(PMPS_GVL.TRANS_SCALING_FACTOR, (BeamParam2.nTran *(PMPS_GVL.TRANS_SCALING_FACTOR+PMPS_PARAM.TRANS_MARGIN))/PMPS_GVL.TRANS_SCALING_FACTOR );
xEvOK := (BeamParam1.neVRange AND BeamParam2.neVRange) = BeamParam1.neVRange;
xRateOK := (BeamParam1.nRate <= BeamParam2.nRate) OR (PMPS_GVL.stCurrentBeamParameters.nMachineMode =1); //ignore in SC mode
xBCOK := ((BeamParam1.nBCRange AND BeamParam2.nBCRange) = BeamParam1.nBCRange) OR (PMPS_GVL.stCurrentBeamParameters.nMachineMode =0); //ignore in NC mode;
FOR idx:=1 to PMPS_GVL.AUX_ATTENUATORS DO
Att1 REF= BeamParam1.astAttenuators[idx];
Att2 REF= BeamParam2.astAttenuators[idx];
xAuxAttOK R= ( Att1.nTran > MIN( PMPS_GVL.TRANS_SCALING_FACTOR, ( (Att2.nTran *(PMPS_GVL.TRANS_SCALING_FACTOR+PMPS_PARAM.TRANS_MARGIN))/PMPS_GVL.TRANS_SCALING_FACTOR ) ) );
END_FOR
FOR idx:=1 to PMPS_GVL.MAX_APERTURES DO
xAuxAprtOK R= BeamParam1.astApertures[idx].Height > BeamParam2.astApertures[idx].Height OR
BeamParam1.astApertures[idx].Width > BeamParam2.astApertures[idx].Width;
END_FOR
F_SafeBPCompare := xAttOK AND xEvOK AND xRateOK AND xAuxAprtOK AND xAuxAttOK AND xBCOK;
END_FUNCTION
F_SafeBPCompare0Rate
(*Compares BeamParam1 to BeamParam2: if the parameters of BeamParam1 are more conservative than BeamParam2
the result will be true. *)
(* When the Mode PV is in Cu 0-rate means this function will return true regardless of other conditions if the beam rate
is zero *)
(* When the Mode PV is in SC 0-bc means this function will return true regardless of other conditions if the beam class
is zero *)
FUNCTION F_SafeBPCompare0Rate : BOOL
VAR_INPUT
BeamParam1 : ST_BeamParams; //Must be more conservative than 2
BeamParam2 : ST_BeamParams;
END_VAR
VAR
// Beam-rate is zero, masks all others considerations.
xZeroRate: BOOL;
//Beam class is zero i.e beam off, masks all other considerations.
xZeroBC : BOOL;
END_VAR
xZeroRate := (BeamParam1.nRate = 0) AND (PMPS_GVL.stCurrentBeamParameters.nMachineMode = 0);
xZeroBC := (BeamParam1.nBCRange <= 1) AND (PMPS_GVL.stCurrentBeamParameters.nMachineMode =1);
F_SafeBPCompare0Rate := (xZeroRate OR xZeroBC) OR F_SafeBPCompare(BeamParam1, BeamParam2);
END_FUNCTION
- Related:
F_SetBeamParams
FUNCTION F_SetBeamParams : ST_BeamParams
VAR_INPUT
nTran : REAL := 0;
neVRange : DWORD := 0;
nRate : UDINT := 0;
nBCRange : WORD := 0;
astAuxAtt : ARRAY [1..PMPS_GVL.AUX_ATTENUATORS] OF ST_PMPS_Attenuator;
END_VAR
VAR
BeamParams : ST_BeamParams;
END_VAR
BeamParams.nTran := LIMIT(0,nTran,PMPS_GVL.TRANS_SCALING_FACTOR);
BeamParams.astAttenuators := astAuxAtt;
BeamParams.neVRange := neVRange;
BeamParams.nRate := MIN(nRate,1000000);
BeamParams.nBCRange := MIN(nBCRange,32767);
F_SetBeamParams := BeamParams;
END_FUNCTION
- Related:
F_SetGoodAttStatus
//Use for testing purposes to set the attenuator status to a good state
FUNCTION F_SetGoodAttStatus : UINT
VAR_INPUT
xStatus : UINT;
END_VAR
VAR
END_VAR
xStatus.0 := 1;
xStatus.3 := 1;
xStatus.4 := 1;
END_FUNCTION
F_SetStateParams
FUNCTION F_SetStateParams : BOOL;
VAR_INPUT
nStateRef : UDINT;
rPosition : REAL;
rTolerance: REAL;
stBeamParams : ST_BeamParams;
END_VAR
VAR_IN_OUT
Table : FB_LinearDeviceStateTable;
END_VAR
VAR
stDeviceState : ST_DeviceState;
END_VAR
stDeviceState.nStateRef := nStateRef;
stDeviceState.rPosition := rPosition;
stDeviceState.rTolerance := rTolerance;
stDeviceState.stReqBeamParam := stBeamParams;
Table.A_Add(
key := nStateRef,
putValue := stDeviceState
);
F_SetStateParams := Table.bOk;
END_FUNCTION
F_ValidReqID
FUNCTION F_ValidReqID : BOOL
VAR_INPUT
ReqID : DWORD;
END_VAR
VAR
END_VAR
F_ValidReqID := ReqID <> PMPS_GVL.EXCLUDED_ASSERTION_ID AND ReqID <> 0;
END_FUNCTION
- Related:
FB_AcceleratorLinks
FUNCTION_BLOCK FB_AcceleratorLinks
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'pytmc' := '
pv: eEnrg:Hgvpu
link: BEND:DMPH:400:BACT
field: EGU GeV
'}
fbHgvpuElectronEnergy : FB_LREALFromEPICS;
{attribute 'pytmc' := '
pv: eEnrg:SXU
link: BEND:DMPS:400:BACT
field: EGU GeV
'}
fbSxuElectronEnergy : FB_LREALFromEPICS;
END_VAR
fbHgvpuElectronEnergy();
fbSxuElectronEnergy();
END_FUNCTION_BLOCK
FB_Arbiter
(* FB Arbiter
A. Wallace 2020-6-26
The arbiter primary objectives are:
- Provide a simple interface for devices to request beam parameter sets
- Provide a way for devices to verify their BPS is active in the arbiter
- Provide a way for devices remove their requests from evaluation
- Evaluate all active beam parameter requests registered with the aribiter,
to determine the safest combination of all sets, provide this set as an output.
- Do all of this with minimal overhead
To these ends, the arbiter uses a hash-table, the rows being a state-id as the key, and a corresponding
beam parameter set to be evaluated against all the other sets (or rows), in the table.
The hash table can be thought of as an array on steriods, they are worth reading about, suffice to say
the hash table will tell you when you reach the end of all the entries, and enables us to find entries quickly.
These features efficiently address the addition, removal, and verification of beam parameter sets listed in the above requirements.
*)
{attribute 'reflection'}
FUNCTION_BLOCK FB_Arbiter IMPLEMENTS I_HigherAuthority, I_LowerAuthority
VAR
nRequestsCount : UDINT; // How many requests are currently in the arbiter
{attribute 'pytmc' := '
pv: AP
io: i
field: DESC Assertion Pool
'}
fbBPAssertionPool : FB_BeamParamAssertionPool; //Table of active beam parameter assertions
xRequestMade : BOOL; // Arbiter has confirmed its request has made it into the beam parameter request
{attribute 'pytmc' := '
pv: ArbiterID
io: i
field: DESC Arbiter ID for elev. req.
'}
nArbiterID : UDINT; // Arbiter ID, used for making higher-level BP requests
nNextCohort : UDINT := 1; // The cohort ID any new requests will adopt, will become ReqInProgCohort at the start of the next acknowledgement cycle
nAckInProgCohort : UDINT := 0; // The cohort ID currently being acknowledged, will become nActiveCohort after acknowledgement from HA
{attribute 'pytmc' := '
pv: CohortCounter
io: i
field: DESC Intrnl cohort counter
'}
nActiveCohort : UDINT := 0; // Requests with cohorts <= to this value will be considered active in CheckRequest
bStartNewAckRequest : BOOL; // Set by an add or remove method call, triggers an ack cycle
bAckInProgress : BOOL; // Set by ElevateReq when there is a new ack request and reset when the ack cycle is complete
// The following IDs are set by the last BP request that was most conservative
// ie. other requests may limit the parameter, but this one is the most recent to limit it
idTransmission : DWORD; // ID of BP limiting transmission
idRate : DWORD; // ID of BP limiting rate
{attribute 'instance-path'}
{attribute 'noinit'}
sPath : T_MaxString;
sArbName : T_MaxString;
InfoStringFmtr : FB_FormatString;
bVerbose : BOOL := FALSE;
END_VAR
VAR_INPUT
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: ArbitratedBP
io: i
field: DESC Arbitrated BP
'}
q_stBeamParams : ST_BeamParams := PMPS_GVL.cstFullBeam; //Updated on each cycle of the arbiter FB with the current arbitrated beam parameter set
q_xStateIDFound : BOOL; //Set true if a state-id is found in the assertion pool after calling A_VerifyAssertion
END_VAR
END_FUNCTION_BLOCK
// Adds a request to the arbiter pool.
// Returns true if the request was successfully added, false if not enough space or a request with the same ID is already present.
METHOD AddRequest : BOOL
VAR_INPUT
nReqID : DWORD; // Unique ID within aribter for the request. Make sure this is unique for every device + state combination
stReqBP : ST_BeamParams; //Requested beam params
sDevName: STRING; // Name of the device making the request
END_VAR
VAR
BP_Int : ST_BP_ArbInternal;
END_VAR
VAR_INST
fbLog : FB_LogMessage;
END_VAR
// If the request is already in the pool, then skip the rest.
// This is flawed, needs to update the request if it's already in the pool, despite having the same ID
IF THIS^.CheckRequestInPool(nReqID)
OR nReqID = PMPS_GVL.EXCLUDED_ASSERTION_ID
OR nReqID = 0 THEN
AddRequest := FALSE;
RETURN;
END_IF
stReqBP.nCohortInt := nNextCohort; // Set the cohort number for this BP request.
// Pack the ID with the request
MEMCPY(ADR(BP_Int), ADR(stReqBP), SIZEOF(stReqBP));
BP_Int.nId := nReqID;
BP_Int.LiveInTable := TRUE;
BP_Int.sDevName := sDevName;
THIS^.fbBPAssertionPool.A_Add(
key := nReqID,
putValue := BP_Int
);
IF NOT THIS^.fbBPAssertionPool.bOk THEN
fbLog(sMsg:=CONCAT('Addition to arbiter assertion pool failed ', DWORD_TO_HEXSTR(nReqID, 4, FALSE)),
eSevr:=TcEventSeverity.Warning,
eSubsystem:=E_Subsystem.MPS,
sJson := F_PMPS_JSON(
sArbName,
sPath,
PMPS_CODES.ARB_FULL));
ELSIF THIS^.fbBPAssertionPool.bOk AND bVerbose THEN
fbLog(sMsg:=CONCAT('Added to pool ',DWORD_TO_HEXSTR(nReqID, 4, FALSE)),
eSevr:=TcEventSeverity.Verbose,
eSubsystem:=E_Subsystem.MPS);
END_IF
// New request, make sure the ack cycle request flag is set
bStartNewAckRequest S= THIS^.fbBPAssertionPool.bOk;
AddRequest := THIS^.fbBPAssertionPool.bOk;
nRequestsCount := nEntryCount;
END_METHOD
// Kernel of the arbiter
// Logic for determining which beam parameter is the most conservative across all request sets.
{attribute 'no_check'}
METHOD INTERNAL ArbitrateBP : ST_BP_ArbInternal
VAR_INPUT
stBP1 : ST_BP_ArbInternal;
stBP2 : ST_BP_ArbInternal;
END_VAR
VAR
idx : UINT := 1;
bcBitmask : WORD:=0;
END_VAR
//<TODO>Add something here to register the key that won for each param
//Attenuation
ArbitrateBP.nTran := MIN(stBP1.nTran, stBP2.nTran);
FOR idx := 1 TO PMPS_GVL.AUX_ATTENUATORS DO
ArbitrateBP.astAttenuators[idx].nTran := MIN(stBP1.astAttenuators[idx].nTran, stBP2.astAttenuators[idx].nTran);
END_FOR
//Apertures
FOR idx := 1 TO PMPS_GVL.MAX_APERTURES DO
IF stBP1.astApertures[idx].Height = 0 OR stBP2.astApertures[idx].Height =0 THEN
ArbitrateBP.astApertures[idx].Height := MAX(stBP1.astApertures[idx].Height, stBP2.astApertures[idx].Height);
ELSE
ArbitrateBP.astApertures[idx].Height := MIN(stBP1.astApertures[idx].Height, stBP2.astApertures[idx].Height);
END_IF
IF stBP1.astApertures[idx].Width = 0 OR stBP2.astApertures[idx].Width =0 THEN
ArbitrateBP.astApertures[idx].Width := MAX(stBP1.astApertures[idx].Width, stBP2.astApertures[idx].Width);
ELSE
ArbitrateBP.astApertures[idx].Width := MIN(stBP1.astApertures[idx].Width, stBP2.astApertures[idx].Width);
END_IF
END_FOR
//Photon energy ranges
ArbitrateBP.neVRange := stBP1.neVRange AND stBP2.neVRange;
//Beam Class ranges
ArbitrateBP.nBCRange := stBP1.nBCRange AND stBP2.nBCRange;
//Beam Class
//Assert the highest allowed beamclass
IF (ArbitrateBP.nBCRange = 0) THEN ArbitrateBP.nBeamClass := 0; END_IF
FOR idx:=1 TO 15 BY 1 DO
IF (idx =1 ) THEN bcBitmask :=1; END_IF
IF (ArbitrateBP.nBCRange AND bcBitmask) = bcBitmask THEN
ArbitrateBP.nBeamClass := TO_USINT(idx);
END_IF
bcBitmask := ROL(bcBitmask,1);
END_FOR
//Rate
ArbitrateBP.nRate := MIN(stBP1.nRate, stBP2.nRate);
END_METHOD
(* Checks request ID is included in arbitration all the way to the accelerator interface
Use like so:
IF fbArbiter.CheckRequest(nStateIDAssertionToCheck) AND (other logic) THEN:
Request is found and active in arbitration,. Do something.
ELSE:
Request was not found, or is not yet included in arbitration. Don't do something/ wait.
*)
METHOD CheckRequest : BOOL
VAR_INPUT
nReqID : DWORD;
END_VAR
VAR
BP : ST_BeamParams;
END_VAR
fbBPAssertionPool.A_Lookup(key := nReqID);
// Verify BP is acknowledged
BP := fbBPAssertionPool.getValue;
// This logic:
// Did we find the assertion in the pool?
// Is the assertion cohort number less than the current cohort and greater than zero?
// Is the Aribter itself active in arbitration?
CheckRequest := fbBPAssertionPool.bOk AND
BP.nCohortInt <= nActiveCohort AND
BP.nCohortInt > 0;
END_METHOD
// Verify request is at least in the local arbiter
// Does not verify request has been included in arbitration.
// Use CheckRequest instead.
METHOD CheckRequestInPool : BOOL
VAR_INPUT
nReqID : DWORD;
END_VAR
THIS^.fbBPAssertionPool.A_Lookup(key := nReqID);
CheckRequestInPool := THIS^.fbBPAssertionPool.bOk;
END_METHOD
// <Arbiter Internal>
// Elevates the arbitrated BP set to something above.
// Could be another arbiter, or a BP requester/ IO,
// or an FB that locks in a specific portion of the BP set.
METHOD ElevateRequest : BOOL
VAR_INPUT
HigherAuthority : I_HigherAuthority;
END_VAR
(*
This method should only be used with one higher level component.
*)
// Complete previous ack cycle
IF bAckInProgress THEN
IF HigherAuthority.CheckRequest(nReqID := nArbiterID) THEN
nActiveCohort := nAckInProgCohort;
bAckInProgress := FALSE;
END_IF
END_IF
// Kick off another ack cycle
IF bStartNewAckRequest AND NOT bAckInProgress THEN
nAckInProgCohort := nNextCohort;
HigherAuthority.RemoveRequest(nReqID:= nArbiterID);
bAckInProgress := HigherAuthority.RequestBP(nReqID := nArbiterID, stReqBP := GetArbitratedBP());
IF bAckInProgress THEN
bStartNewAckRequest := FALSE;
nNextCohort := nNextCohort + 1;
END_IF
END_IF
END_METHOD
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
nID : DWORD := PMPS_GVL.EXCLUDED_ASSERTION_ID; // Arbiter ID, must be globally unique cannot be zero or 16#FFFF
END_VAR
nArbiterID := nId;
fbBPAssertionPool.A_Reset();
sArbName := CONCAT('Arbiter ID: 0x', DWORD_TO_HEXSTR(nID, 4, FALSE));
END_METHOD
// Executes Arbitration between all requested beam parameter sets
METHOD INTERNAL GetArbitratedBP : ST_BeamParams
VAR_INPUT
END_VAR
VAR
getPosPtr : POINTER TO T_HashTableEntry := 0;
getBPStructInt : ST_BP_ArbInternal;
stOutputBP : ST_BP_ArbInternal; //Holding struct for arbitration process
END_VAR
VAR_INST
xFirstPass : BOOL := TRUE;
fbGetCurTaskIdx : GETCURTASKINDEX;
LastCycleCount : UDINT;
fbLogMessage : FB_LogMessage;
END_VAR
//Arbitrate
(*
This step cycles through every hash table entry, comparing
the beam parameters from each assertion to what we're planning
to assert.
The safer of the two parameters is kept so at the end we may
have a mix of beam parameters, the composition being safe for
all asserters.
*)
THIS^.fbBPAssertionPool.A_GetFirst( putPosPtr := 0, getPosPtr=>getPosPtr, getValue=>getBPStructInt );
IF THIS^.fbBPAssertionPool.bOk THEN //If the table is empty (not OK), we are left with the default stOutputBP set above.
//The first entry in the hash table is taken as the setting to arbitrate against
stOutputBP := getBPStructInt;
REPEAT
THIS^.fbBPAssertionPool.A_GetNext( putPosPtr := THIS^.fbBPAssertionPool.getPosPtr, getPosPtr=>getPosPtr, getValue=>getBPStructInt );
IF THIS^.fbBPAssertionPool.bOk THEN
stOutputBP := ArbitrateBP(stOutputBP, getBPStructInt);
END_IF
UNTIL NOT fbBPAssertionPool.bOk
END_REPEAT
ELSE
//Full beam if there are no arbitration requests
MEMCPY(ADR(stOutputBP), ADR(PMPS_GVL.cstFullBeam), SIZEOF(PMPS_GVL.cstFullBeam));
END_IF
MEMCPY(ADR(q_stBeamParams),ADR(stOutputBP), SIZEOF(q_stBeamParams));
GetArbitratedBP := stOutputBP;
GetArbitratedBP.xValidToggle := NOT GetArbitratedBP.xValidToggle;
xFirstPass := FALSE;
END_METHOD
(* Removes request from abritration. *)
METHOD RemoveRequest : BOOL
VAR_INPUT
nReqId : DWORD;
END_VAR
VAR_INST
fbLog : FB_LogMessage;
END_VAR
VAR CONSTANT
BP_Int : ST_BP_ArbInternal := (nId:=0, LiveInTable:=FALSE);
END_VAR
IF nReqId <> PMPS_GVL.EXCLUDED_ASSERTION_ID AND CheckRequestInPool(nReqId) THEN
// Include an A_Add action here to
// update this entry with a dummy filler (ID = 0, LiveInTable= False)
// before removing it since this hash
// table needs to be viewed with a diagnostic
// and so the entries need to appear clean.
THIS^.fbBPAssertionPool.A_Add(key := nReqID, putValue := BP_Int);
THIS^.fbBPAssertionPool.A_Remove(key := nReqId);
IF bVerbose THEN
IF THIS^.fbBPAssertionPool.bOk THEN
fbLog(sMsg:=CONCAT('Removed from pool: ', DWORD_TO_HEXSTR(nReqID,4,FALSE)),
eSevr:=TcEventSeverity.Verbose,
eSubsystem:=E_Subsystem.MPS);
ELSE
fbLog(sMsg:=CONCAT('Failed to remove from pool: ', DWORD_TO_HEXSTR(nReqID,4,FALSE)),
eSevr:=TcEventSeverity.Warning,
eSubsystem:=E_Subsystem.MPS);
END_IF
END_IF
// With any new request reset the internal arbiter "done" flag
bStartNewAckRequest S= THIS^.fbBPAssertionPool.bOk;
RemoveRequest := THIS^.fbBPAssertionPool.bOk;
nRequestsCount := nEntryCount;
END_IF
END_METHOD
METHOD RequestBP : BOOL
VAR_INPUT
(*StateID of state requesting beam parameter set*)
nReqID : DWORD;
(*Requested beam params*)
stReqBP : ST_BeamParams;
END_VAR
RequestBP := AddRequest(nReqID := nReqID, stReqBP := stReqBP, sDevName:=sArbName);
END_METHOD
// How many entries are in the arbiter now
PROPERTY nEntryCount : UDINT
VAR
END_VAR
fbBPAssertionPool.A_Count();
IF fbBPAssertionPool.bOk THEN
nEntryCount := fbBPAssertionPool.nCount ;
END_IF
END_PROPERTY
PROPERTY nLowerAuthorityID : DWORD
VAR
END_VAR
nLowerAuthorityID := nArbiterID;
END_PROPERTY
FB_Arbiter_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_Arbiter_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
Arbitration();
RequestCheckRemoveBP();
FullArbitrationStack();
StackArbiters();
RemoveReq();
DoS();
END_FUNCTION_BLOCK
METHOD ArbiterCapacity
VAR_INPUT
END_VAR
VAR
nId : DWORD := 1;
stReq : ST_BeamParams := (nTran:=12);
nRandID : DWORD;
testReq : ST_BeamParams := (neVRange := 16#FFEE, nRate:=33);
idx : DWORD;
END_VAR
VAR_INST
fbArbCapacityTest : FB_Arbiter(1);
Cycle : INT := 0;
CycleLimit : INT := 20;
END_VAR
VAR CONSTANT
SysID : DWORD := 42;
END_VAR
TEST('Arbiter Capcity');
// Fill the arbiter
FOR idx := 1 TO PMPS_PARAM.MAX_ASSERTIONS DO
fbArbCapacityTest.AddRequest(nReqID := idx, stReqBP := testReq, sDevName:= CONCAT('Device #', TO_STRING(idx)));
END_FOR
TEST_FINISHED();
END_METHOD
METHOD Arbitration
VAR_INPUT
END_VAR
VAR
nTranReqID : UDINT := 1;
stAttReqBP : ST_BeamParams := PMPS_GVL.cstFullBeam;
nPPEReqID : UDINT := 2;
stPPEReqBP : ST_BeamParams := PMPS_GVL.cstFullBeam;
nEVReqID : UDINT := 3;
stEVReqBP : ST_BeamParams := PMPS_GVL.cstFullBeam;
nRateReqID : UDINT := 4;
stRateReqBP : ST_BeamParams := PMPS_GVL.cstFullBeam;
nBCRangeReqID : UDINT := 5;
stBCReqBP : ST_BeamParams := PMPS_GVL.cstFullBeam;
stArbitratedBP : ST_BeamParams;// := PMPS_GVL.cstFullBeam;;
stExpectedBP : ST_BeamParams := PMPS_GVL.cstFullBeam;
END_VAR
VAR_INST
fbArbiter : FB_Arbiter(1);
END_VAR
TEST('Arbitration');
stAttReqBP.nTran := 0.50;
stEVReqBP.neVRange := 2#1111_1111_1111_1111_1111_1100_1111_1111;
stRateReqBP.nRate := 10;
stBCReqBP.nBCRange := 2#0111_0000_0000_0000;
stBCReqBP.nBeamClass := 15;
stExpectedBP.nTran := stAttReqBP.nTran;
stExpectedBP.neVRange := stEVReqBP.neVRange;
stExpectedBP.nRate := stRateReqBP.nRate;
stExpectedBP.nBCRange := stBCReqBP.nBCRange;
stExpectedBP.nBeamClass := stBCReqBP.nBeamClass;
//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nTranReqID, stAttReqBP, sDevName:= 'Device' ),
'Safest attenuation request failed to load');
//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nPPEReqID, stPPEReqBP, sDevName:= 'Device'),
'Safest PPE request failed to load');
//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nEVReqID, stEVReqBP, sDevName:= 'Device'),
'Safest stEVReqBP request failed to load');
//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nRateReqID, stRateReqBP, sDevName:= 'Device'),
'Safest stRateReqBP request failed to load');
//Load arbiter with beam parameter sets
AssertTrue(fbArbiter.AddRequest(nBCRangeReqID, stBCReqBP, sDevName:= 'Device'),
'Safest stBCReqBP request failed to load');
stArbitratedBP := fbArbiter.GetArbitratedBP();
AssertFalse(F_DifferentBeamParams(stExpectedBP, stArbitratedBP),
'Arbitrated BP does not match expected.');
TEST_FINISHED();
END_METHOD
METHOD DoS
VAR
nOrigReqID : UDINT := 30;
nSecondReqID : UDINT := 35;
nPeskyReqID : UDINT := 40;
END_VAR
VAR_INST
DosArb : FB_Arbiter(20);
DummyHA : FB_DummyHA := (ReqAcknowledged:=FALSE); // Set req ack false to start so we can control when it is verified
nTimeoutCounter : UINT;
bInit : BOOL;
bSecond : BOOL;
eDosTestCases : E_ArbDosTestStates;
END_VAR
VAR CONSTANT
nTimeoutCounterMAX : UINT := 50;
nWaitCount : UINT := 10;
nWait2ndCount : UINT := nWaitCount + 5;
END_VAR
TEST('DoS');
CASE eDosTestCases OF
E_ArbDosTestStates.Init: // Init, first request
DosArb.AddRequest(nOrigReqID, PMPS_GVL.cst0RateBeam, sDevName:= 'Device');
eDosTestCases := E_ArbDosTestStates.SecondReq;
E_ArbDosTestStates.SecondReq: // Add second request
DosArb.AddRequest(nSecondReqID, PMPS_GVL.cst0RateBeam, sDevName:= 'Device');
eDosTestCases := E_ArbDosTestStates.WaitForAck;
E_ArbDosTestStates.WaitForAck:
IF nTimeoutCounter > nWaitCount THEN
DummyHA.ReqAcknowledged := TRUE;
eDosTestCases := E_ArbDosTestStates.WaitFor2ndAck;
END_IF
E_ArbDosTestStates.WaitFor2ndAck:
DummyHA.ReqAcknowledged := False;
IF nTimeoutCounter > nWait2ndCount THEN
DummyHA.ReqAcknowledged := TRUE;
eDosTestCases := E_ArbDosTestStates.RunAsserts;
END_IF
END_CASE
DosArb.ElevateRequest(DummyHA);
// Pesky request always adding and removing
DosArb.RemoveRequest(nPeskyReqID);
DosArb.AddRequest(nPeskyReqID, PMPS_GVL.cst0RateBeam, sDevName:= 'Device');
nTimeoutCounter := nTimeoutCounter + 1;
IF nTimeoutCounter > nTimeoutCounterMAX or eDosTestCases = E_ArbDosTestStates.RunAsserts THEN
AssertTrue(DosArb.CheckRequest(nOrigReqID), 'Original request still not accepted');
AssertTrue(DosArb.CheckRequest(nSecondReqID), 'Second request still not accepted');
TEST_FINISHED_NAMED('DoS');
END_IF
END_METHOD
{attribute 'no_check'}
METHOD FullArbitrationStack
VAR_INPUT
END_VAR
VAR
nId : DWORD := 1;
stReq : ST_BeamParams := (nTran:=12);
nIdDoS : DWORD := 13;
stDoSReq : ST_BeamParams := PMPS_GVL.cstFullBeam;
END_VAR
VAR_INST
fbArbiter : FB_Arbiter(1);
fbHigherArb : FB_Arbiter(2);
fbSubSysToArb : FB_SubSysToArbiter_IO; // Subsystem interface with beamline arbiter PLC
pBPR : POINTER TO ST_BeamParams_IO;
fbArbToSubSys : FB_ArbiterToSubSys_IO := (RequestingSystemID:=42); // Beamline arbiter PLC interface with subsystems
pBPC : POINTER TO ST_BeamParams_IO;
fbBPR : FB_BPRequestor; // Use in the arbiter PLC to apply an arbitrated beam parameter set to the accelerator interface
FFO : FB_HardwareFFOutput; // Dummy FFO not important for these tests
Cycle : INT := 0;
CycleLimit : INT := 20;
END_VAR
VAR CONSTANT
SysID : DWORD := 42;
END_VAR
// This simulates a synchronous cycle between the subsystem and arbiter PLC cycles. Ie. phase is locked, and cycle time is the same. This is not always the case, so these tests should be
// trusted with a grain of salt.
TEST('FullStack');
CASE Cycle OF
0:
IF NOT fbArbiter.CheckRequestInPool(nID) THEN
AssertTrue(fbArbiter.AddRequest(nId, stReq,'Device'), 'Arbiter returned false from AddRequest'); // some device asking its local arbiter for some beam parameters
END_IF
TEST('JustEnteredRequest');
AssertFalse(fbArbiter.CheckRequest(nId),
'Check should fail. Request not arbitrated yet.');
TEST_FINISHED();
8000:
TEST_FINISHED_NAMED('FullStack');
END_CASE
TEST('VerifyBPRHasBP');
IF fbHigherArb.CheckRequest(SysID) THEN
AssertFalse(F_DifferentBeamParams(stReq, fbBPR.q_ReqBP),
'Beam parameters not fully propagated to BPR at highest level');
TEST_FINISHED_NAMED('VerifyBPRHasBP');
END_IF
TEST('ArbToSubsysPropagation');
IF fbArbToSubSys.nActiveCohort = fbArbToSubSys.nRequestedCohort
AND fbArbToSubSys.nActiveCohort <> 0 THEN
AssertTrue(fbHigherArb.CheckRequest(SysID),
'Active cohort = Requested cohort before higher arbiter confirmed request was active');
TEST_FINISHED_NAMED('ArbToSubsysPropagation');
END_IF
TEST('SubsysToArbPropagation');
IF fbArbiter.CheckRequest(nId) THEN
AssertTrue(fbSubSysToArb.nActiveCohort >= fbSubSysToArb.nRequestCohort,
'Request validated before higher level confirmation');
TEST_FINISHED_NAMED('SubsysToArbPropagation');
END_IF
TEST('VerifyIncludedAtAllLevels');
IF fbArbiter.CheckRequest(nId) THEN
AssertTrue(fbHigherArb.CheckRequest(fbArbToSubSys.RequestingSystemID), 'HigherArb is not yet included in arbitration');
AssertTrue(fbSubSysToArb.CheckRequest(fbArbiter.nLowerAuthorityID), 'Local Arb is not yet included in arbitration');
TEST_FINISHED_NAMED('VerifyIncludedAtAllLevels');
END_IF
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////
//Ethercat transfer simulation
// Transfer of requested BP to arbiter PLC
pBPR := ADR(fbArbToSubSys.i_RequestedBP);
pBPR^ := fbSubSysToArb.q_stRequestedBP;
// Transfer of current BP to sub system PLC
pBPC := ADR(fbSubSysToArb.i_stCurrentBP);
pBPC^ := fbArbToSubSys.o_CurrentBP;
//////////////////////////////////////
//Start of arbiter plc cycle
//Arb io collects sub system status
fbArbToSubSys(Arbiter := fbHigherArb, fbFFHWO := FFO); // This is making a request in the higher arb
//BPR collects arbiter summation
fbBPR(Arbiter := fbHigherArb); // This is getting the arbitrated BP
IF CycleLimit > Cycle THEN
Cycle := Cycle + 1;
ELSE
Cycle := 8000;
END_IF
END_METHOD
METHOD RemoveReq
VAR
Arb : FB_Arbiter(20);
DummyHA : FB_DummyHA;
Result : BOOL;
END_VAR
Arb.AddRequest(30, PMPS_GVL.cst0RateBeam, 'Device');
Arb.ElevateRequest(DummyHA);
TEST('Remove Req');
Result := Arb.RemoveRequest(77);
AssertFalse( Result, 'RemoveReq returned true, with nothing to remove');
AssertFalse( Arb.bStartNewAckRequest , 'StartNewAckRequest is set. Something is wrong');
TEST_FINISHED();
END_METHOD
METHOD RequestCheckRemoveBP
VAR_INPUT
END_VAR
VAR
nId : DWORD := 1;
stReq : ST_BeamParams;
END_VAR
VAR_INST
fbArbiter : FB_Arbiter(1);
END_VAR
TEST('RequestBP');
AssertTrue(fbArbiter.AddRequest(nId, stReq, 'Device'),
'Request failed');
TEST_FINISHED();
TEST('DuplicateID');
AssertFalse(fbArbiter.AddRequest(nId, stReq, 'Device'),
'Duplicate request accepted, not good.');
TEST_FINISHED();
TEST('CheckBP');
AssertFalse(fbArbiter.CheckRequest(nId),
'Check should fail. Request not arbitrated yet.');
TEST_FINISHED();
TEST('CheckBPArbitrated');
WRITE_PROTECTED_ULINT(ADR(fbArbiter.nActiveCohort), fbArbiter.nNextCohort);
AssertTrue(fbArbiter.CheckRequest(nId),
'Check should pass.');
TEST_FINISHED();
TEST('IDDoesNotExist');
AssertFalse(fbArbiter.CheckRequest(nId+1),
'Check returned true when ID does not exist');
TEST_FINISHED();
TEST('RemoveBP');
AssertTrue(fbArbiter.RemoveRequest(nId),
'Remove failed');
AssertFalse(fbArbiter.CheckRequest(nId),
'Remove did not remove request');
TEST_FINISHED();
TEST('NoRequestsFullBeam');
AssertFalse(F_DifferentBeamParams(fbArbiter.GetArbitratedBP(), PMPS_GVL.cstFullBeam), 'Arbitrated beam should be full with all requests removed');
TEST_FINISHED();
END_METHOD
METHOD StackArbiters
VAR_INPUT
END_VAR
VAR
nId : DWORD := 1;
stReq : ST_BeamParams := (nTran:=12);
END_VAR
VAR_INST
fbArbiter : FB_Arbiter(1);
fbHigherArb : FB_Arbiter(2);
ArbBP : ST_BeamParams;
Cycle : INT := 0;
CycleLimit : INT := 20;
END_VAR
VAR CONSTANT
SysID : DWORD := 42;
END_VAR
TEST('StackingArbiters');
fbArbiter.AddRequest(nReqID:=nId, stReqBP:=stReq, sDevName :='Device');
fbArbiter.ElevateRequest(fbHigherArb);
ArbBP := fbHigherArb.GetArbitratedBP();
AssertTrue(ArbBP.nTran = 12, 'We should see the transmission number here');
TEST_FINISHED();
END_METHOD
FB_ArbiterToSubSys_IO
(* Exchanges preemption requests and current beam state with subsystems in arbiter
network.
2019-12-2, A. Wallace
Use in conjunction with FB_SubSysToArbiter_IO.
This FB runs in the beamline arbiter PLC (BAP). There should be one of these for every
subsystem interfaced to the BAP. Each subsystem PLC should run a sibling FB_SubSysToArbiter_IO FB.
This FB and its subordinate communicate through the ethercat connection between the subsystem PLC and the BAP.
A beam parameter set request is sent from the subsystem to this PLC, and includes a cohort number.
This cohort number (i_RequestedBP.nCohortInt) is compared to a local cohort number (nRequestedCohort).
A request from the subsystem to update or modify its current request is indicated by an increase in the
i_RequestedBP.nCohortInt value. Then this function block updates the local arbiter request for the subsystem
and awaits confirmation. Once the new request is confirmed in the preemptive system this function block increments
the ActiveCohort value to match the nRequestedCohort. ActiveCohort is also relayed back to the subsystem
as the active cohort value in the o_CurrentBP set. The subsystem then knows its request has been heard,
and can carry out its own acknowledgement to its various requestors.
Things to note:
If a subsystem has any new or changed preemptive requests you should see the cohort value increment a bit.
If the cohort number is incrementing out of control, it usually means the subsysetm has some preemptive
request loop (two arbiters elevating to each other), or there is a RequestAdd, RequestRemove loop.
*)
FUNCTION_BLOCK FB_ArbiterToSubSys_IO
VAR_INPUT
RequestingSystemID : DWORD := PMPS_GVL.EXCLUDED_ASSERTION_ID; // System ID for making requests to the supreme arbiter
sName : STRING := 'ArbiterToSubSys';
Reset : BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
Arbiter : FB_Arbiter;
fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR
i_RequestedBP AT %I* : ST_BeamParams_IO; // Requested BP from subsystem
{attribute 'pytmc' := '
pv: RequestedBP:FromSubSys
'}
RequestedBP: ST_BeamParams;
o_CurrentBP AT %Q* : ST_BeamParams_IO; // Current BP set
// EL6692 Diagnostics
i_Connected AT %I* : BOOL; // SYNC Inputs^External device not connected !!! Doesn't really work.
{attribute 'pytmc' := 'pv: WcState
io: i
field: DESC Working counter state.
field: ZNAM OK
field: ONAM Error'}
i_WcState AT %I* : BOOL := TRUE; // WcState^WcState
{attribute 'pytmc' := 'pv: TxPDO_state
io: i
field: DESC PDO Transmission is OK
field: ZNAM OK
field: ONAM Error'}
i_TxPDOState AT %I* : BOOL := TRUE; // SYNC Inputs^TxPDO state
{attribute 'pytmc' := 'pv: TxPDO_toggle
io: i
field: DESC PDO Transmission is OK
field: ZNAM OK
field: ONAM Error'}
i_TxPDOToggle AT %I* : BOOL; // TxPDO toggle
// Fast faults
ffPMPSIO_Disconnect : FB_FastFault := (i_Desc:='Issue w/ arbiter interface to subsystem, verify subsystem is OK, or ethercat connection.'); // Fast fault for ethercat issues
nActiveCohort : UDINT := 0; // Active cohort. Updated to the req. number from the sub system after request is confirmed.
nRequestedCohort : UDINT := 0;
rtToggle : R_TRIG;
END_VAR
// Incoming request from subsystem PLC
RequestedBP := IO_TO_BP(i_RequestedBP);
// Update Arbiter with our request
rtToggle(CLK:= i_RequestedBP.xValid); // If interface resumes, reset/catch up
// xValid will go false if the PLC program on the other side is stopped. Ironically the ethercat interface continues happily.
// If subsystem cohort has incremented, update beam parameter request with our arbiter
IF rtToggle.Q THEN
nActiveCohort := 0;
END_IF
IF i_RequestedBP.nCohortInt > nRequestedCohort OR rtToggle.Q THEN
Arbiter.RemoveRequest(RequestingSystemID);
Arbiter.AddRequest(RequestingSystemID, RequestedBP,sName);
nRequestedCohort := i_RequestedBP.nCohortInt;
END_IF
// Check if latest request is acknowledged by this arbiter
// Acknowledgement happens after an arbitration cycle, which means if RequestedCohort just incremented
// due to a new request, CheckRequest will be false.
IF Arbiter.CheckRequest(RequestingSystemID) THEN
nActiveCohort := nRequestedCohort; // If it is, then the active cohort is set to the requested cohort number.
END_IF
// Sending current beam parameter set to subsystems
o_CurrentBP := BP_TO_IO(PMPS_GVL.stCurrentBeamParameters);
o_CurrentBP.nCohortInt := nActiveCohort;
//////////////////////////////////////
// Verifying the ethercat interface health
/////////////////////////////////////
// Both of these values should be 0 for a fully valid exchange
ffPMPSIO_Disconnect.i_xOK := NOT i_WcState AND NOT i_TxPDOState;
ffPMPSIO_Disconnect(
io_fbFFHWO := fbFFHWO,
i_xReset := Reset,
i_DevName := sName,
i_TypeCode:=5);
END_FUNCTION_BLOCK
FB_ArbToSubsys_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_ArbToSubsys_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
BasicFunction();
END_FUNCTION_BLOCK
{attribute 'no_check'}
METHOD BasicFunction
VAR_INPUT
END_VAR
VAR CONSTANT
SysID : DWORD := 42;
END_VAR
VAR
nId : DWORD := 1;
stReq : ST_BeamParams := (nTran:=12);
END_VAR
VAR_INST
fbArbiter : FB_Arbiter(1);
pBPR : POINTER TO ST_BeamParams_IO;
stBPRIO : ST_BeamParams_IO := (
nCohortInt := 1,
nTran := 35); // Simulates subsystem BP request
fbArbToSubSys : FB_ArbiterToSubSys_IO := (RequestingSystemID:=42); // Beamline arbiter PLC interface with subsystems
pBPC : POINTER TO ST_BeamParams_IO;
fbBPR : FB_BPRequestor; // Use in the arbiter PLC to apply an arbitrated beam parameter set to the accelerator interface
FFO : FB_HardwareFFOutput; // Dummy FFO not important for these tests
Cycle : INT := 0;
CycleLimit : INT := 20;
END_VAR
// Simulate an initial cycle with no new requests coming in
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
TEST('Init');
TEST_FINISHED();
//Ethercat transfer simulation
// Transfer of requested BP to arbiter PLC
pBPR := ADR(fbArbToSubSys.i_RequestedBP);
pBPR^ := stBPRIO;
///////////////////////////////////////
//Sub system cycle
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
//END of sub system cycle
///////////////////////////////////////
TEST('New request');
AssertTrue(fbArbToSubSys.nRequestedCohort = fbArbToSubSys.i_RequestedBP.nCohortInt, 'ReqCohort and qBP Cohort do not match, and they should');
AssertTrue(fbArbiter.CheckRequestInPool(SysID), 'ArbToSubSys request should be in arbiter pool now');
AssertTrue(fbArbToSubSys.nActiveCohort = 0, 'Active cohort should remain the initial value until the current request is active in higher arbitration');
AssertTrue(fbArbToSubSys.o_CurrentBP.nCohortInt = fbArbToSubSys.nActiveCohort, 'Cohort being sent to sub system remains unincremented');
TEST_FINISHED_NAMED('New request');
///////////////////////////////////////
//Sub system cycle
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
//END of sub system cycle
///////////////////////////////////////
///////////////////////////////////////
//Sub system cycle
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
//END of sub system cycle
///////////////////////////////////////
///////////////////////////////////////
//Sub system cycle
fbBPR(Arbiter:=fbArbiter);
fbArbToSubSys(Arbiter:=fbArbiter, fbFFHWO:=FFO);
//END of sub system cycle
///////////////////////////////////////
TEST('Request has become active');
AssertTrue(fbArbToSubSys.nRequestedCohort = fbArbToSubSys.nActiveCohort, 'ReqCohort and qBP Cohort do not match, and they should');
AssertTrue(fbArbiter.CheckRequest(SysID), 'ArbToSubSys request should be in arbiter pool now');
AssertTrue(fbArbToSubSys.nActiveCohort = stBPRIO.nCohortInt, 'Active cohort should remain the initial value until the current request is active in higher arbitration');
AssertTrue(fbBPR.q_ReqBP.nTran = stBPRIO.nTran, 'BPR and requested BP should match');
TEST_FINISHED_NAMED('Request has become active');
END_METHOD
FB_AttenuatorSimulator
FUNCTION_BLOCK FB_AttenuatorSimulator
VAR_INPUT
i_rRequestedAttenuation : REAL;
i_stBPReadback : ST_BeamParams;
END_VAR
VAR_OUTPUT
q_xFault : BOOL;
q_rCurrentAttenuation : REAL;
END_VAR
VAR_IN_OUT
Arbiter : FB_Arbiter; //Higher level arbiter from which upstream attenuation can be requested
END_VAR
VAR
tonDelay : TON := (
PT := T#3S
);
END_VAR
// Really basic delay
tonDelay(IN := i_rRequestedAttenuation <> q_rCurrentAttenuation);
IF tonDelay.Q THEN
q_rCurrentAttenuation := i_rRequestedAttenuation;
END_IF
END_FUNCTION_BLOCK
- Related:
FB_BeamClassFromEPICS
FUNCTION_BLOCK FB_BeamClassFromEPICS
(*
enum MPSBeamClass {
Beam Off = 0,
Kicker STBY = 1,
BC1Hz = 2,
BC10Hz = 3,
Diagnostic = 4,
BC120Hz = 5,
Tuning = 6,
1% MAP = 7,
5% MAP = 8,
10% MAP = 9,
25% MAP = 10,
50% MAP = 11,
100% MAP = 12,
FULL = 13,
SPARE = 14,
SPARE = 15
}
*)
VAR_IN_OUT
BP : ST_BeamParams;
fbMPS_BeamClass : FB_LREALFromEPICS;
FFO : FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
xError : BOOL;
END_VAR
VAR
ffBeamClassReadBack : FB_FastFault := (
i_DevName := 'Arbiter',
i_Desc := 'Issue with beam class readback from Accelerator. Gateway or EPICS connection. Must be fixed.',
i_TypeCode := 16#213,
i_xAutoReset:=True);
END_VAR
VAR CONSTANT
cFailSafeBC : USINT := 16;
END_VAR
fbMPS_BeamClass();
IF fbMPS_BeamClass.bValid THEN
BP.nBeamClass := LREAL_TO_USINT(fbMPS_BeamClass.fValue);
ELSE
BP.nBeamClass := cFailSafeBC;
END_IF
BP.nBCRange := TO_WORD(BP.nBeamClass);
ffBeamClassReadback(i_xOK:=fbMPS_BeamClass.bValid, io_fbFFHWO:=FFO);
BP.xValid R= NOT fbMPS_BeamClass.bValid;
END_FUNCTION_BLOCK
- Related:
FB_BeamClassOutputs
(*
Sets the beam class assertion lines for a given beam class.
*)
{attribute 'no_check'}
FUNCTION_BLOCK FB_BeamClassOutputs
VAR_INPUT
BP : ST_BeamParams;
END_VAR
VAR_OUTPUT
END_VAR
VAR
nBeamClass : BYTE;
wBeamClass : BYTE;
InitCounter: BYTE;
counter : INT;
// Beam class lines are restricted to 8 channels in the current design, since
// there are no plans to use all 16. Channels 1-7 may be allocated to any other
// beam classes so long as they are ordered least to greatest.
// Channel 8 is reserved for full beam.
{attribute 'pytmc' := 'pv: BeamClassChannel
io: i
field: DESC Hardwire channel state'}
epicsBitmap : WORD;
{attribute 'TcLinkTo' := '[1] := TIIB[PMPS_Premp]^Channel 1^Output;
[2] := TIIB[PMPS_Premp]^Channel 2^Output;
[3] := TIIB[PMPS_Premp]^Channel 3^Output;
[4] := TIIB[PMPS_Premp]^Channel 4^Output;
[5] := TIIB[PMPS_Premp]^Channel 5^Output;
[6] := TIIB[PMPS_Premp]^Channel 6^Output;
[7] := TIIB[PMPS_Premp]^Channel 7^Output;
[8] := TIIB[PMPS_Premp]^Channel 8^Output;'} // 8 - Full beam
q_BC_ASSERTION_LINES AT %Q* : ARRAY [1..MAX_BEAM_CLASS_LINES] OF BOOL;
END_VAR
VAR CONSTANT
//Limited to total of 6 digital outputs
MAX_BEAM_CLASS_LINES : BYTE := 8;
BC_1HZ : BYTE := 1;
BC_10HZ : BYTE := 2;
BC_FULL : BYTE := 16;
END_VAR
// Determine BC
IF BP.nRate >= 120 THEN
nBeamClass := BC_FULL;
ELSIF BP.nRate >= 10 THEN
nBeamClass := BC_10HZ;
ELSIF BP.nRate >= 1 THEN
nBeamClass := BC_1HZ;
ELSE
nBeamClass := 0;
END_IF
//Assert Beam Class
////////////////////////////////////
//0x0 = 0000 0000 0000 0000
//0x1 = 0000 0000 0000 0001
//0xF = 1111 1111 1111 1111
//Initialize BC lines to zero on every pass
FOR InitCounter := 1 TO MAX_BEAM_CLASS_LINES DO
q_BC_ASSERTION_LINES[InitCounter] := FALSE;
END_FOR
//Set BC lines according to beam class
//A BC of 0x0 would pass over this loop, setting none of the lines high
// , as FOR loops check the initialized variable at the top to see if it's >
// than the "TO" variable.
FOR wBeamClass:=1 TO MIN(MAX_BEAM_CLASS_LINES-1, nBeamClass) DO
q_BC_ASSERTION_LINES[wBeamClass] := TRUE;
END_FOR
q_BC_ASSERTION_LINES[8] := nBeamClass = 16; //Set channel 8 true if BC is 16
// Readbacks for EPICS
epicsBitmap.0 := q_BC_ASSERTION_LINES[1];
epicsBitmap.1 := q_BC_ASSERTION_LINES[2];
epicsBitmap.2 := q_BC_ASSERTION_LINES[3];
epicsBitmap.3 := q_BC_ASSERTION_LINES[4];
epicsBitmap.4 := q_BC_ASSERTION_LINES[5];
epicsBitmap.5 := q_BC_ASSERTION_LINES[6];
epicsBitmap.6 := q_BC_ASSERTION_LINES[7];
epicsBitmap.7 := q_BC_ASSERTION_LINES[8];
END_FUNCTION_BLOCK
- Related:
FB_BeamClassOutputs_BCD
(*
Sets the beam class assertion lines for a given beam class.
*)
{attribute 'no_check'}
FUNCTION_BLOCK FB_BeamClassOutputs_BCD
VAR_INPUT
BP : ST_BeamParams;
END_VAR
VAR_OUTPUT
END_VAR
VAR
nBeamClass : BYTE;
wBeamClass : BYTE;
InitCounter: BYTE;
counter : INT;
// Beam class lines are restricted to 4 channels in the current design.
// There are 16 possible beamclasses with 0 -15 with 0 no beam.
// First out put is the LSB and bit 4 is MSB
{attribute 'pytmc' := 'pv: BeamClassChannel
io: i
field: DESC Hardwire channel state'}
epicsBitmap : WORD;
{attribute 'TcLinkTo' := '[1] := TIIB[PMPS_Premp]^Channel 1^Output;
[2] := TIIB[PMPS_Premp]^Channel 2^Output;
[3] := TIIB[PMPS_Premp]^Channel 3^Output;
[4] := TIIB[PMPS_Premp]^Channel 4^Output;'}
q_BC_ASSERTION_LINES AT %Q* : ARRAY [1..MAX_BEAM_CLASS_LINES] OF BOOL;
END_VAR
VAR CONSTANT
//Limited to total of 4 digital outputs
MAX_BEAM_CLASS_LINES : BYTE := 4;
END_VAR
//Initialize BC lines to zero on every pass
FOR InitCounter := 1 TO MAX_BEAM_CLASS_LINES DO
q_BC_ASSERTION_LINES[InitCounter] := FALSE;
END_FOR
//Set BC lines according to beam class
// There are 4 digital outputs the BC is going to be a BCD
q_BC_ASSERTION_LINES[1] := BP.nBeamClass.0;
q_BC_ASSERTION_LINES[2] := BP.nBeamClass.1;
q_BC_ASSERTION_LINES[3] := BP.nBeamClass.2;
q_BC_ASSERTION_LINES[4] := BP.nBeamClass.3;
// Readbacks for EPICS
epicsBitmap.0 := q_BC_ASSERTION_LINES[1];
epicsBitmap.1 := q_BC_ASSERTION_LINES[2];
epicsBitmap.2 := q_BC_ASSERTION_LINES[3];
epicsBitmap.3 := q_BC_ASSERTION_LINES[4];
END_FUNCTION_BLOCK
- Related:
FB_BeamClassWatcher
(*
M. Ghaly
The Beam class watcher ensures the current and target beam class is within
the arbirated bounds.
The abritrated bounds come from a simple AND of all the permitted beam class ranges. See
the arbitrate action of the arbiter FB. It also reads a PV and ensures that the asserted Beam Class
is the same as the PV from the ATCA crate
Note, this protection logic does not account for beam-off when determining fast-fault
status. If a device is requesting a limited range of BC, this request must be honored,
regardless of current beam-rate.
*)
{attribute 'reflection'}
FUNCTION_BLOCK FB_BeamClassWatcher
VAR_INPUT
i_stCurrentBeamParams : ST_BeamParams; //Link to global beam params
i_stMachineTargetBeamParams : ST_BeamParams; //Link to global machine target beam params
i_stRequestedBeamParams : ST_BeamParams; //Link to arbiter output or beam param. requestor
//Auto Reset fault
i_xAutoReset: BOOL;
sName : STRING := 'BeamClassWatcher';
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
io_fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR
xBeamClassWithinBounds : BOOL;
fbFF : FB_FastFault :=(
i_DevName := sName,
i_Desc := 'Fault occurs when the asserted beamclass or pv beam class falls outside the permitted range.',
i_TypeCode := 7 );
{attribute 'pytmc' := '
pv: ResidualBeamClass
io: i
archive: 1Hz monitor
field: EGU bc-bitmask
'}
bcResidual : DWORD;
fbLog : FB_LogMessage := (
eSubSystem := E_Subsystem.MPS,
eSevr := TcEventSeverity.Critical
);
bLogOneShot : BOOL;
sDevName : T_MaxString := 'Beam Class Watcher';
fbGetHN : FB_GetHostName;
bInit : BOOL := TRUE;
{attribute 'instance-path'}
{attribute 'noinit'}
sPath : T_MaxString;
fbStr : FB_FormatString := (
sOut := 'Non-zero beam class residual: %32b; Req: %32b; Act: %32b');
END_VAR
IF bInit THEN
fbGetHN(bExecute:=TRUE);
bInit R= NOT fbGetHN.bBusy;
END_IF
xBeamClassWithinBounds := (i_stCurrentBeamParams.nBCRange AND i_stRequestedBeamParams.nBCRange) = i_stCurrentBeamParams.nBCRange
AND (i_stCurrentBeamParams.nBeamClass <= i_stRequestedBeamParams.nBeamClass);
bcResidual := (i_stCurrentBeamParams.nBCRange XOR i_stRequestedBeamParams.nBCRange) AND i_stCurrentBeamParams.nBCRange;
IF bcResidual <> 0 AND bLogOneShot THEN
fbLog.sJson := F_PMPS_JSON(
CONCAT(fbGetHN.sHostName, sDevName),
sPath,
PMPS_CODES.PEW_FAULT);
fbStr.arg1 := F_DWORD(bcResidual);
fbStr.arg2 := F_WORD(i_stCurrentBeamParams.nBCRange);
fbStr.arg3 := F_WORD(i_stRequestedBeamParams.nBCRange);
fbStr();
fbLog(sMsg:=fbStr.sOut);
bLogOneShot := FALSE;
ELSIF bcResidual = 0 THEN
bLogOneShot := TRUE;
END_IF
fbFF(i_xOK := xBeamClassWithinBounds OR (i_stCurrentBeamParams.nMachineMode = 0),
io_fbFFHWO := io_fbFFHWO, i_xAutoReset := i_xAutoReset);
END_FUNCTION_BLOCK
FB_BeamParamAssertionPool
(* This function block implements simple database. Data element values are stored in the hash table. *)
{attribute 'no_check'}
FUNCTION_BLOCK FB_BeamParamAssertionPool
VAR_INPUT
key : DWORD := 0;(* Entry key: used by A_Lookup, A_Remove method, the key variable is also used by A_Add method *)
putPosPtr : POINTER TO T_HashTableEntry := 0;(* Hash table entry position pointer (used by A_Find, A_GetNext, A_GetPrev) *)
putValue : ST_BP_ArbInternal;(* Hash table entry value (used by A_AddHead, A_AddTail, A_Find )*)
END_VAR
VAR_OUTPUT
bOk : BOOL := FALSE;(* TRUE = Success, FALSE = Failed *)
getPosPtr : POINTER TO T_HashTableEntry := 0;(* Returned hash table entry position pointer *)
getValue : ST_BP_ArbInternal;(* Returned hash table entry value *)
nCount : UDINT := 0;(* Hash table size (number of used entries, used by A_Count) *)
END_VAR
VAR
{attribute 'pytmc' := '
pv: Entry
io: i
'}
epicsDataPool : ARRAY[1..PMPS_PARAM.MAX_ASSERTIONS] OF ST_BP_ArbInternal;(* Structured data element pool for display in EPICS*)
dataPool : ARRAY[0..PMPS_PARAM.MAX_ASSERTIONS*3] OF ST_BP_ArbInternal;(* Structured data element pool *)
entries : ARRAY[0..PMPS_PARAM.MAX_ASSERTIONS*3] OF T_HashTableEntry;(* Max. number of hash table entries. The value of table entry = 32 bit integer (pointer to dataPool-array-entry) *)
fbTable : FB_HashTableCtrl;(* basic hash table control function block *)
hTable : T_HHASHTABLE;(* hash table handle *)
pRefPtr : POINTER TO ST_BP_ArbInternal := 0;
indexOfElem : ULINT;(* Integer value (max. size: x86=>32bit, x64=>64bit)*)
cstSafeBeam :ST_BeamParams := ( nTran := 0, neVRange := 0, nRate := 0, nBCRange :=0);// MG
END_VAR
;
END_FUNCTION_BLOCK
ACTION A_Add:
(* Adds entry to the table *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
fbTable.A_Add( hTable := hTable, key := key, putValue := ADR(cstSafeBeam)(* we will set this value later *), getPosPtr=>getPosPtr, bOk=>bOk );(* Add new element to the table, getPosPtr points to the new entry *)
IF fbTable.bOk THEN(* Success *)
fbTable.A_GetIndexAtPosPtr( hTable := hTable, putPosPtr := getPosPtr, getValue =>indexOfElem, bOk=>bOk );(* Get array index of getPosPtr entry *)
IF fbTable.bOk THEN(* Success *)
pRefPtr := ADR( dataPool[indexOfElem] );(* Get pointer to the data element *)
pRefPtr^ := putValue;(* copy application value *)
fbTable.A_Add( hTable := hTable, key := key, putValue := pRefPtr, bOk=>bOk );(* Assign the entry value = pointer to the data element *)
IF fbTable.bOk THEN(* Success *)
getValue := putValue;
END_IF
END_IF
END_IF
DataPoolToEpics();
END_ACTION
ACTION A_Count:
(* Count number of used entries *)
nCount := hTable.nCount;
bOk := TRUE;
END_ACTION
ACTION A_GetFirst:
(* Get first entry position pointer and value *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
fbTable.A_GetFirst( hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := pRefPtr^;
END_IF
END_ACTION
ACTION A_GetNext:
(* Get next entry position pointer and value *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
bOk := FALSE;
IF putPosPtr = 0 THEN
RETURN;
END_IF
fbTable.A_GetNext( hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := pRefPtr^;
END_IF
END_ACTION
ACTION A_Lookup:
(* Lookup for entry by key *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
fbTable.A_Lookup( key := key, hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := pRefPtr^;
END_IF
END_ACTION
ACTION A_Remove:
(* Search for entry and remove it *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
fbTable.A_Remove( key := key, hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := pRefPtr^;
END_IF
DataPoolToEpics();
END_ACTION
ACTION A_Reset:
(* Reset/initialize linked list *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
bOk := F_CreateHashTableHnd( ADR( entries ), SIZEOF( entries ), hTable );(* Intialize table handle *)
fbTable.A_Reset( hTable := hTable, bOk=>bOk );
nCount := 0;
END_ACTION
ACTION DataPoolToEpics:
MEMCPY(ADR(epicsDataPool), ADR(dataPool), (PMPS_PARAM.MAX_ASSERTIONS) * SIZEOF(ST_BP_ArbInternal));
END_ACTION
FB_BPControlDevice
(*
Used to request a beam parameter set from EPICS.
Technically just one of these is needed in the entire
PMPS for a line.
*)
FUNCTION_BLOCK FB_BPControlDevice
VAR_INPUT
(* Requested pre-optic attenuation % *)
{attribute 'pytmc' := 'pv: ReqBP:Transmission
io: o
field: HOPR 1;
field: LOPR 0;
field: PREC 2;
'}
nTran : REAL := PMPS_GVL.TRANS_SCALING_FACTOR;
(* Pulse-rate *)
{attribute 'pytmc' := 'pv: ReqBP:Rate
io: o
field: EGU Hz
'}
nRate : UDINT := 10;
(* Photon energy ranges *)
{attribute 'pytmc' := 'pv: ReqBP:PhotonEnergyRanges
io: o
field: EGU eV'}
{attribute 'displaymode' := 'binary'}
neVRange : DWORD := 4294967295;
(* Beamclass ranges *)
{attribute 'pytmc' := 'pv: ReqBP:BeamClassRanges
io: o'}
{attribute 'displaymode' := 'binary'}
nBCRange : WORD := 32767;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
Arbiter : FB_Arbiter;
END_VAR
VAR
EpicsReqBP : ST_BeamParams;
{attribute 'pytmc' := '
pv: ReqBP:Apply
io: o
'}
bApply : BOOL;
nControlDeviceID : DWORD;
sControlDeviceName: STRING:= 'BP Control Device';
rtApply : R_TRIG;
END_VAR
EpicsReqBP.nTran := nTran;
EpicsReqBP.neVRange := neVRange;
EpicsReqBP.nRate := nRate;
EpicsReqBP.nBCRange := nBCRange;
rtApply(CLK:=bApply);
IF rtApply.Q THEN
Arbiter.RemoveRequest(nControlDeviceID);
Arbiter.AddRequest(nControlDeviceID, EpicsReqBP, sControlDeviceName);
bApply := FALSE;
END_IF
END_FUNCTION_BLOCK
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
nID : DWORD := PMPS_GVL.EXCLUDED_ASSERTION_ID;
END_VAR
nControlDeviceID:= nID;
END_METHOD
- Related:
FB_BPRequestor
(* BP Requestor
Elevates the BP request from an arbiter to PMPS_GVL.stRequestedBP
*)
FUNCTION_BLOCK FB_BPRequestor IMPLEMENTS I_HigherAuthority
VAR_INPUT
END_VAR
VAR_OUTPUT
q_ReqBP : ST_BeamParams; // Arbitrated BP
END_VAR
VAR_IN_OUT
Arbiter : FB_Arbiter;
//FFO : FB_HardwareFFOutput;
END_VAR
VAR
ReqBP : ST_BeamParams;
nCohort : ULINT; // Current cohort, inc. 1 cycle after IO is updated
nRequestCohort : ULINT; // Cohort number recorded at the time of the request.
END_VAR
Arbiter.ElevateRequest(THIS^); // Executes arbitration and retrieves ReqBP
IF nRequestCohort >= nCohort THEN
// Update requested BP set
q_ReqBP := ReqBP;
// Note: we may do more here someday... perhaps all of the output control
// will reside in here, and be solely controlled by this block. For now
// I am going to adopt a flat management structure.
// Start a new cohort
nCohort := nCohort + 1;
END_IF
END_FUNCTION_BLOCK
// Returns true when outputs to MPS are updated
METHOD CheckRequest : BOOL
VAR_INPUT
nReqID : DWORD;
END_VAR
VAR_INST
xFirstTime : BOOL;
nId : DWORD;
END_VAR
(* nRequestCohort will be < nCohort after output control
function blocks have been updated to reflect the new request
*)
CheckRequest := nRequestCohort < nCohort;
END_METHOD
METHOD RemoveRequest : BOOL
VAR_INPUT
nReqID : DWORD; //StateID to remove
END_VAR
END_METHOD
METHOD RequestBP : BOOL
VAR_INPUT
(*StateID of state requesting beam parameter set*)
nReqID : DWORD;
(*Requested beam params*)
stReqBP : ST_BeamParams;
END_VAR
VAR_INST
ReqID : DWORD;
Registered : BOOL := FALSE;
END_VAR
// Check the request is coming from the same source we're used to
IF NOT Registered THEN
ReqID := nReqID;
Registered := TRUE;
END_IF
// log a complaint if false
RequestBP := ReqId = nReqId;
// Update internal BP request struct
ReqBP := stReqBP;
// Record current cohort
nRequestCohort := nCohort;
END_METHOD
FB_BPTM_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_BPTM_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
VAR CONSTANT
BPTM_ARR : UDINT := PMPS_PARAM.MAX_ASSERTIONS;
cCycle : UINT := 20; // How many cycles does it take to complete a BPTM
END_VAR
BPTMBasicFunction();
InterruptAtAllSteps();
FullArbError();
END_FUNCTION_BLOCK
METHOD BPTMBasicFunction
VAR_INPUT
END_VAR
VAR
//Final and transition assertions
nTransitionID : UDINT := 1;
stTransitionAssertion : ST_BeamParams := (nRate := 10, nBCRange:=0);
nReqID : UDINT := 2;
stReqAssertion : ST_BeamParams := (nTran := 0.2, nBCRange:=2#0000_0000_0000_0011);
END_VAR
VAR_INST
fbBPTM_TestBasicFunction : BeamParameterTransitionManager;
fbArbiter : FB_Arbiter(1);
ffo : FB_HardwareFFOutput;
fbSubSysIO : FB_DummyArbIO;
xFirstPass : BOOL := TRUE;
eTestStep: E_BPTMTestStates := E_BPTMTestStates.Init;
END_VAR
// Completes arbiter request elevation process
// Necessary for CheckRequest to return true, ever
fbBPTM_TestBasicFunction(
fbArbiter := fbArbiter,
);
fbSubSysIO(LA := fbArbiter, FFO := ffo);
// Note: this struct may also be (over)written to in the tests below, specifically rate.
fbBPTM_TestBasicFunction.stCurrentBeamParameters := fbSubSysIO.q_stSimulatedBPReadback;
//MG: Need to set the Machine mode testing SC
PMPS_GVL.stCurrentBeamParameters.nMachineMode :=0;
CASE eTestStep OF
E_BPTMTestStates.Init:
TEST('BPTMTest');
eTestStep := E_BPTMTestStates.WaitingForBeam;
E_BPTMTestStates.WaitingForBeam:
TEST('BPTM Waits for Beam');
fbBPTM_TestBasicFunction.i_TransitionAssertionID := nTransitionID;
fbBPTM_TestBasicFunction.i_stTransitionAssertion := stTransitionAssertion;
fbBPTM_TestBasicFunction.i_nRequestedAssertionID := nReqID;
fbBPTM_TestBasicFunction.i_stRequestedAssertion := stReqAssertion;
IF fbBPTM_TestBasicFunction.eBPTMState = E_BPTMState.WaitForBP AND
NOT fbBPTM_TestBasicFunction.xEntry THEN
AssertTrue(fbArbiter.CheckRequestInPool(nTransitionID), 'Arbiter did not accept BPTM transition assertion');
AssertTrue(fbArbiter.CheckRequestInPool(nReqID), 'Arbiter did not accept BPTM transition assertion');
AssertFalse(fbBPTM_TestBasicFunction.q_xTransitionAuthorized,
'Transition should not be authorized until requests are in and beam is ready');
AssertTrue(fbBPTM_TestBasicFunction.bBusy, 'Busy should be true here');
AssertFalse(fbBPTM_TestBasicFunction.bDone, 'Done should be false');
AssertFalse(fbBPTM_TestBasicFunction.bError, 'Error should be false here');
TEST_FINISHED_NAMED('BPTM Waits for Beam');
eTestStep := E_BPTMTestStates.Transitioning;
END_IF
E_BPTMTestStates.Transitioning:
TEST('BPTM Authorizes Transition');
fbBPTM_TestBasicFunction.stCurrentBeamParameters.nRate := 0; // satisfies the transition request
fbBPTM_TestBasicFunction.stCurrentBeamParameters.nBeamClass := 0; // satisfies the transition request
IF fbBPTM_TestBasicFunction.eBPTMState = E_BPTMState.Transitioning AND
NOT fbBPTM_TestBasicFunction.xEntry THEN
AssertTrue(fbArbiter.CheckRequest(nTransitionID),
'Transition assertion should be in arbiter');
AssertTrue(fbArbiter.CheckRequest(nReqID),
'Final assertion should be in arbiter');
AssertTrue(fbBPTM_TestBasicFunction.q_xTransitionAuthorized,
'Transition should be authorized at this point.');
AssertEquals(nReqID, fbBPTM_TestBasicFunction.nCurrentAssertionID,
'nCurrentAssertionID not set.');
TEST_FINISHED_NAMED('BPTM Authorizes Transition');
eTestStep := E_BPTMTestStates.WaitingForFinalAssertion;
END_IF
E_BPTMTestStates.WaitingForFinalAssertion:
TEST('BPTM Waits for final BP');
fbBPTM_TestBasicFunction.i_xDoneMoving := TRUE;
IF fbBPTM_TestBasicFunction.eBPTMState = E_BPTMState.WaitForFinalBP THEN
AssertTrue(fbArbiter.CheckRequest(nTransitionID),
'Transition assertion should be in arbiter');
TEST_FINISHED_NAMED('BPTM Waits for final BP');
eTestStep := E_BPTMTestStates.CleaningUp;
END_IF
E_BPTMTestStates.CleaningUp:
fbSubSysIO.AutoUpdateBP := TRUE;
TEST('Cleaning up');
IF fbBPTM_TestBasicFunction.eBPTMState = E_BPTMState.Done THEN
AssertFalse(fbArbiter.CheckRequestInPool(nTransitionID), 'Transition req should be removed from arb now');
eTestStep := E_BPTMTestStates.Done;
TEST_FINISHED_NAMED('Cleaning up');
END_IF
E_BPTMTestStates.Done:
AssertTrue(fbBPTM_TestBasicFunction.bDone, 'Done should be set');
AssertFalse(fbBPTM_TestBasicFunction.bError, 'Error should be cleared');
AssertFalse(fbBPTM_TestBasicFunction.bBusy, 'Busy should be false');
TEST_FINISHED_NAMED('BPTMTest');
END_CASE
END_METHOD
// Simulate error due to full arbiter
METHOD FullArbError
VAR_INPUT
END_VAR
VAR
//Final and transition assertions
nTransitionID : UDINT := 1000;
stTransitionAssertion : ST_BeamParams := ( nTran := 0, neVRange := 0, nRate := 0);//PMPS_GVL.cstSafeBeam;
nReqID : UDINT := 2000;
stReqAssertion : ST_BeamParams := ( nTran := 0, neVRange := 0, nRate := 0);//PMPS_GVL.cstSafeBeam;
idx : UDINT;
nRandID : DWORD;
testReq : ST_BeamParams := (neVRange := 16#FFEE, nRate:=33);
END_VAR
VAR_INST
fbBPTM_TestFullArb : BeamParameterTransitionManager;
fbArbFullErr : FB_Arbiter(1);
ffo : FB_HardwareFFOutput;
fbSubSysIO : FB_DummyArbIO;
fbBPR : FB_BPRequestor;
xFirstPass : BOOL := TRUE;
eTestStep: E_BPTMTestStates := E_BPTMTestStates.Init;
tonRetryTimeout : TON := (PT:=T#5s);
entryToRetry : bool := true;
fbRand : DRAND := (Seed :=1);
END_VAR
CASE eTestStep OF
E_BPTMTestStates.Init:
TEST('BPTMErrorFullArb');
// Fill the arbiter
FOR idx := 1 TO PMPS_PARAM.MAX_ASSERTIONS // this fills the arbiter
- fbBPTM_TestFullArb.cReqArbCapacity + 1 DO // but test just one over the allowed amount
fbArbFullErr.AddRequest(nReqID := idx, stReqBP := testReq, sDevName :='Device');
END_FOR
fbSubSysIO.AutoUpdateBP := TRUE;
AssertTrue(PMPS_PARAM.MAX_ASSERTIONS - fbArbFullErr.nEntryCount < fbBPTM_TestFullArb.cReqArbCapacity,
'Arbiter is not full enough, rest of tests will be invalid');
eTestStep := E_BPTMTestStates.Error;
E_BPTMTestStates.Error:
TEST('BPTM Full Error');
IF fbBPTM_TestFullArb.bError THEN
AssertTrue(fbBPTM_TestFullArb.bError AND fbBPTM_TestFullArb.nErrId = PMPS_CODES.NoRoomInArb,
'Incorrect error response from full arbiter.');
TEST_FINISHED_NAMED('BPTM Full Error');
eTestStep := E_BPTMTestStates.Retry;
END_IF
E_BPTMTestStates.Retry:
TEST('Test Retry Post Failed Final');
// Clear the arbiter
if entryToRetry THEN
FOR idx := 1 TO PMPS_PARAM.MAX_ASSERTIONS + 1 DO
fbArbFullErr.RemoveRequest(idx);
END_FOR
entryToRetry := FALSE;
END_IF
// Push the retry button
fbBPTM_TestFullArb.bRetry := TRUE;
tonRetryTimeout(IN:=TRUE);
IF tonRetryTimeout.Q OR fbBPTM_TestFullArb.q_xTransitionAuthorized THEN
AssertTrue(fbBPTM_TestFullArb.q_xTransitionAuthorized, 'Transition should have been authorized by now');
AssertTrue(fbArbFullErr.CheckRequest(nTransitionID), 'Transition ID is missing from arbiter while transition is authorized');
AssertTrue(fbArbFullErr.CheckRequest(nReqID), 'Final ID is missing from arbiter while transition is authorized');
TEST_FINISHED_NAMED('Test Retry Post Failed Final');
eTestStep := E_BPTMTestStates.CleaningUp;
END_IF
E_BPTMTestStates.CleaningUp:
TEST_FINISHED_NAMED('BPTMErrorFullArb');
END_CASE
fbBPTM_TestFullArb(
fbArbiter := fbArbFullErr,
i_TransitionAssertionID := nTransitionID,
i_stTransitionAssertion := stTransitionAssertion,
i_nRequestedAssertionID := nReqID,
i_stRequestedAssertion := stReqAssertion,
stCurrentBeamParameters := fbSubSysIO.q_stSimulatedBPReadback,
);
fbSubSysIO(LA := fbArbFullErr, FFO := ffo);
END_METHOD
// Test the BPTM seamlessly handles new requests at any stage
// Verify old requests are removed from arbiter
{attribute 'no_check'}
METHOD InterruptAtAllSteps
VAR_INPUT
END_VAR
VAR
//Final and transition assertions
nTransitionID : UDINT := 16#DAAD;
stTranReq : ST_BeamParams := PMPS_GVL.cst0RateBeam;
nFirstReqID : UDINT := 16#FEED;
stFirstReq : ST_BeamParams := (nTran :=1, nRate := 16#FEED, neVRange :=2#1111_1111_1111_1111_1111_1111_1111_1111);
END_VAR
VAR_INST
fbBPTM_InterruptionTest : BeamParameterTransitionManager;
ffo : FB_HardwareFFOutput := (bAutoReset:=TRUE);
fbSubSysIO : FB_DummyArbIO;
fbBPR : FB_BPRequestor;
xFirstPass : BOOL := TRUE;
eTestStep: E_BPTMTestStates := E_BPTMTestStates.Init;
tonRetryTimeout : TON := (PT:=T#5s);
fbBPTMDiscCycles : BeamParameterTransitionManager;
fbSubSysIODisc : FB_DummyArbIO;
fbArbDisc : FB_Arbiter(1000);
iCount : UINT;
aBPTM : ARRAY [1..cCycle] OF BeamParameterTransitionManager;
BPTMUnderTest : REFERENCE TO BeamParameterTransitionManager;
fbArbiter : FB_Arbiter(1);
aArbIO : ARRAY [1..cCycle] OF FB_DummyArbIO;
ArbIO : REFERENCE TO FB_DummyArbIO;
nCycleLim : UINT;
nCycle : UINT;
sCurrentTestName : STRING :='';
bCycleLimHit : BOOL;
nAllStepsDone : BYTE;
tTimeOut : TON := (PT:=T#20ms);
bTestedOnce : BOOl;
END_VAR
VAR CONSTANT
nNewTarget : INT := 0;
nRequestBP : INT := 1;
nWaitForBP : INT := 2;
nTransitioning : INT := 3;
nWaitForFinalBP : INT := 4;
nCleaningUp : INT := 5;
nDone : INT := 6;
nIdle : INT := 7;
END_VAR
(* These tests will confirm the BPTM is interruptable at any step,
meaning it will not deadlock or stall, and will always complete with
the correct sequencing *)
(* This functionality is crucial as a deadlocked BPTM will need manual intervention,
users of the BPTM should always be able to just set a target BP that is easy to reach
at any point in time, and have the BPTM head in that direction and succeed. *)
// Determine how many cycles it would take normally, to complete a full BPTM cycle
// This count will be verified against the current count constant
IF NOT bTestedOnce THEN
TEST('CheckBPTM Cycle Count');
WHILE fbBPTMDiscCycles.eBPTMState <> E_BPTMState.Idle DO
fbBPTMDiscCycles.i_TransitionAssertionID := nTransitionID;
fbBPTMDiscCycles.i_stTransitionAssertion := stTranReq;
fbBPTMDiscCycles.i_nRequestedAssertionID := nFirstReqID;
fbBPTMDiscCycles.i_stRequestedAssertion := stFirstReq;
fbBPTMDiscCycles(fbArbiter:=fbArbDisc);
fbSubSysIODisc(LA:=fbArbDisc, FFO:=ffo);
fbBPTMDiscCycles.stCurrentBeamParameters := fbSubSysIODisc.q_stSimulatedBPReadback;
fbSubSysIODisc.AutoUpdateBP := TRUE;
fbBPTMDiscCycles.i_xDoneMoving S= fbBPTMDiscCycles.q_xTransitionAuthorized;
iCount := iCount + 1;
END_WHILE
IF fbBPTMDiscCycles.eBPTMState = E_BPTMState.Idle THEN
AssertTrue(cCycle >= iCount, CONCAT('cCycle is too low, increase it to', INT_TO_STRING(iCount)));
TEST_FINISHED_NAMED('CheckBPTM Cycle Count');
END_IF
// Run BPTM for nCycleLim cycles, switch to a new target, verify BPTM completes anyways
FOR nCycleLim := 1 TO cCycle DO
nCycle := 0;
bCycleLimHit := FALSE;
BPTMUnderTest REF= aBPTM[nCycleLim];
ArbIO REF= aArbIO[nCycleLim];
BPTMUnderTest.i_TransitionAssertionID := nTransitionID;
BPTMUnderTest.i_stTransitionAssertion := stTranReq;
ArbIO.AutoUpdateBP := TRUE;
// Start the test for this Cycle Limit
sCurrentTestName := CONCAT('CycleLimTest:',INT_TO_STRING(nCycleLim));
TEST(sCurrentTestName);
nAllStepsDone := 0;
tTimeOut(IN:=FALSE);
WHILE nAllStepsDone <> 16#FF DO
tTimeOut(IN:= TRUE);
IF tTimeOut.Q THEN
AssertTrue(FALSE, CONCAT('Failed ', BYTE_TO_HEXSTR(nAllStepsDone, 2, FALSE)));
EXIT;
END_IF
// Verify all steps complete
IF bCycleLimHit THEN
nAllStepsDone.nNewTarget S= BPTMUnderTest.eBPTMState = E_BPTMState.NewTarget;
nAllStepsDone.nRequestBP S= BPTMUnderTest.eBPTMState = E_BPTMState.RequestBP;
nAllStepsDone.nWaitForBP S= BPTMUnderTest.eBPTMState = E_BPTMState.WaitForBP;
nAllStepsDone.nTransitioning S= BPTMUnderTest.eBPTMState = E_BPTMState.Transitioning;
nAllStepsDone.nWaitForFinalBP S= BPTMUnderTest.eBPTMState = E_BPTMState.WaitForFinalBP;
nAllStepsDone.nCleaningUp S= BPTMUnderTest.eBPTMState = E_BPTMState.CleaningUp;
nAllStepsDone.nDone S= BPTMUnderTest.eBPTMState = E_BPTMState.Done;
nAllStepsDone.nIdle S= BPTMUnderTest.eBPTMState = E_BPTMState.Idle;
ELSE
nAllStepsDone := 0;
END_IF
// After nCycleLim cycles (nCycle), change the target
IF nCycle >= nCycleLim THEN
BPTMUnderTest.i_nRequestedAssertionID := nCycleLim;
BPTMUnderTest.i_stRequestedAssertion.nRate := nCycleLim;
bCycleLimHit := TRUE;
ELSE
BPTMUnderTest.i_nRequestedAssertionID := nFirstReqID;
BPTMUnderTest.i_stRequestedAssertion := stFirstReq;
END_IF
BPTMUnderTest(fbArbiter:=fbArbiter);
ArbIO(LA:=fbArbiter, FFO := ffo);
BPTMUnderTest.stCurrentBeamParameters := ArbIO.q_stSimulatedBPReadback;
BPTMUnderTest.i_xDoneMoving := BPTMUnderTest.q_xTransitionAuthorized;
nCycle := nCycle + 1;
END_WHILE
AssertTrue(fbArbiter.CheckRequestInPool(BPTMUnderTest.i_nRequestedAssertionID),
CONCAT('Final req not in pool: ', UINT_TO_STRING(nCycleLim)));
AssertFalse(fbArbiter.CheckRequestInPool(BPTMUnderTest.i_TransitionAssertionID),
'TransReq still in pool');
AssertFalse(fbArbiter.CheckRequestInPool(nFirstReqID),
'First req still in pool');
AssertTrue(bCycleLimHit,
CONCAT('Cycle lim not hit', UINT_TO_STRING(nCycleLim)));
AssertTrue(ArbIO.q_stSimulatedBPReadback.nRate = BPTMUnderTest.i_stRequestedAssertion.nRate,
'Rates are not equal...');
TEST_FINISHED_NAMED(sCurrentTestName);
// Clean up for next test
fbArbiter.RemoveRequest(nFirstReqID);
fbArbiter.RemoveRequest(nTransitionID);
fbArbiter.RemoveRequest(nCycleLim);
END_FOR
END_IF
END_METHOD
// Test the BPTM seamlessly handles new requests at any stage
// Verify old requests are removed from arbiter
METHOD InterruptedTransition
VAR_INPUT
END_VAR
VAR
//Final and transition assertions
nTransitionID : UDINT := 3;
stTranReq : ST_BeamParams := PMPS_GVL.cstSafeBeam;
nFirstReqID : UDINT := 1;
stFirstReq : ST_BeamParams := (nRate := 100);
nSecondReqID : UDINT := 2;
stSecReq : ST_BeamParams := (nRate:= 200);
END_VAR
VAR_INST
fbBPTM_InterruptionTest : BeamParameterTransitionManager;
fbArbiter : FB_Arbiter(1);
ffo : FB_HardwareFFOutput;
fbSubSysIO : FB_DummyArbIO;
fbBPR : FB_BPRequestor;
xFirstPass : BOOL := TRUE;
eTestStep: E_BPTMTestStates := E_BPTMTestStates.Init;
tonRetryTimeout : TON := (PT:=T#5s);
END_VAR
CASE eTestStep OF
E_BPTMTestStates.Init:
TEST('BPTM Intr Trans');
fbBPTM_InterruptionTest.i_TransitionAssertionID := nTransitionID;
fbBPTM_InterruptionTest.i_stTransitionAssertion := stTranReq;
fbBPTM_InterruptionTest.i_nRequestedAssertionID := nFirstReqID;
fbBPTM_InterruptionTest.i_stRequestedAssertion := stFirstReq;
fbSubSysIO.AutoUpdateBP := TRUE; // Enable auto update
eTestStep := E_BPTMTestStates.WaitingForTransitionAssertion;
E_BPTMTestStates.WaitingForBeam:
IF fbBPTM_InterruptionTest.eBPTMState = E_BPTMState.WaitingForTransitionAssertion AND
NOT fbBPTM_InterruptionTest.xEntry THEN // since we're not in entry anymore
//change the request to interrupt
fbBPTM_InterruptionTest.i_nRequestedAssertionID := nSecondReqID;
fbBPTM_InterruptionTest.i_stRequestedAssertion := stSecReq;
eTestStep := E_BPTMTestStates.Transitioning;
END_IF
E_BPTMTestStates.Transitioning:
TEST('New Target Gets To Trans Auth');
tonRetryTimeout(IN:=TRUE);
IF tonRetryTimeout.Q OR fbBPTM_InterruptionTest.q_xTransitionAuthorized THEN
AssertTrue(fbBPTM_InterruptionTest.q_xTransitionAuthorized, 'Transition should have been authorized by now');
AssertTrue(fbArbiter.CheckRequest(nTransitionID), 'Transition ID is missing from arbiter while transition is authorized');
AssertTrue(fbArbiter.CheckRequest(nSecondReqID), 'Final ID is missing from arbiter while transition is authorized');
TEST_FINISHED_NAMED('New Target Gets To Trans Auth');
eTestStep := E_BPTMTestStates.CleaningUp;
END_IF
E_BPTMTestStates.CleaningUp:
TEST_FINISHED_NAMED('BPTM Intr Trans');
END_CASE
fbBPTM_InterruptionTest(
fbArbiter := fbArbiter,
stCurrentBeamParameters := fbSubSysIO.q_stSimulatedBPReadback
);
fbSubSysIO(LA := fbArbiter, FFO := ffo);
END_METHOD
FB_CTLS_Outputs
(*
Controls auxiliary beam class outputs.
1-3 are mapped to control the Cu beamline rate via the LCLS II MPS.
Maps beam rate requests to 1, 10, or full rate beam for Cu linac.
*)
FUNCTION_BLOCK FB_CTLS_Outputs
VAR_INPUT
BP : ST_BeamParams;
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'pytmc' := 'pv: CTLSChannel
io: i
field: DESC CTLS Rate Control hardwire channel state'}
epicsBitmap : WORD;
{attribute 'TcLinkTo' := '[1] := TIIB[PMPS_Premp]^Channel 9^Output;
[2] := TIIB[PMPS_Premp]^Channel 10^Output;
[3] := TIIB[PMPS_Premp]^Channel 11^Output;'}
q_CLTS_ASSERTION_LINES AT %Q* : ARRAY [1..3] OF BOOL;
END_VAR
// CLTS 1Hz
q_CLTS_ASSERTION_LINES[1] := BP.nRate >= 1;
// CLTS 10Hz
q_CLTS_ASSERTION_LINES[2] := BP.nRate >= 10;
// CLTS Full rate
q_CLTS_ASSERTION_LINES[3] := BP.nRate >= 120;
epicsBitMap.0 := q_CLTS_ASSERTION_LINES[1];
epicsBitMap.1 := q_CLTS_ASSERTION_LINES[2];
epicsBitMap.2 := q_CLTS_ASSERTION_LINES[3];
END_FUNCTION_BLOCK
- Related:
FB_DiffBP_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_DiffBP_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
BPDiff();
END_FUNCTION_BLOCK
//Verify BP comparison remains logical.
METHOD BPDiff
VAR_INPUT
END_VAR
VAR
BP1 : ST_BeamParams;
BP2 : ST_BeamParams;
END_VAR
TEST('BPDiffCheck');
BP2.nTran := 10;
AssertTrue(
F_DifferentBeamParams(BP1, BP2),
'DifferentBP eval is broken (True)');
BP1.nTran := BP2.nTran;
AssertFalse(
F_DifferentBeamParams(BP2, BP1),
'DifferentBP eval is broken (False)');
TEST_FINISHED();
END_METHOD
- Related:
FB_DummyArbIO
(* Use to test other library integrations of PMPS preemptive functionality *)
(* This block stands in for the FB_SubSysToArb_IO block. *)
FUNCTION_BLOCK FB_DummyArbIO IMPLEMENTS I_HigherAuthority, I_LowerAuthority
VAR_INPUT
//stCurrentBP : REFERENCE TO ST_BeamParams; //Set to something to redirect currentBP update
LA : I_LowerAuthority;
tRateDelay : TIME; // Delay until rate is looped back into q_stSimulatedBPReadback
tTransDelay : TIME; // Delay until transmission is looped back into q_stSimulatedBPReadback
tPEDelay : TIME; // Delay until PE is looped back into q_stSimulatedBPReadback
tAptDelay : TIME; // Delay until apertures are looped back into q_stSimulatedBPReadback
tSattDelay : TIME; // Delay until apertures are looped back into q_stSimulatedBPReadback
END_VAR
VAR_OUTPUT
q_stSimulatedBPReadback : ST_BeamParams; // Updated to
END_VAR
VAR_IN_OUT
FFO : FB_HardwareFFOutput;
END_VAR
VAR
bAutoUpdateBP : BOOL := FALSE; // Set by AutoUpdateBP property, if true causes this FB to update PMPS_GVL.stCurrentBP to the
// requested value automatically within the RequestBP method and restore it within the RemoveRequest method
stNewReqBP : ST_BeamParams;
stReqOutBP : ST_BeamParams;// To be written on next ApplyBPReq call
stSaveCurrentBP : ST_BeamParams; // Will be restored after RemoveRequest then ApplyBPReq call
nSavedID : DWORD; // Used to simulate CheckRequest method
tonRate : TON;
tonTrans : TON;
tonPE : TON;
tonApt : TON;
tonSatt : TON;
tonBCRange: TON;
tonBC :TON;
END_VAR
LA.ElevateRequest(THIS^);
ApplyBPReq(FALSE); //If autoupdate is set, this will propagate the request to PMPS_GVL.stCurrentBP;
END_FUNCTION_BLOCK
// Call this method to update q_stSimulatedBPReadback at will, used if auto-update is set to false
METHOD ApplyBPReq
VAR_INPUT
bUpdateBP : BOOL := FALSE; // Set true and call this method to set q_stSimulatedBPReadback to the request
END_VAR
VAR
BP : REFERENCE TO ST_BeamParams;
END_VAR
IF bUpdateBP THEN
stSaveCurrentBP := q_stSimulatedBPReadback;
q_stSimulatedBPReadback := stReqOutBP;
ELSIF bAutoUpdateBP THEN
tonApt.IN := 0 <> MEMCMP(ADR(stReqOutBP.astApertures), ADR(q_stSimulatedBPReadback.astApertures), SIZEOF(q_stSimulatedBPReadback.astApertures));
tonApt(PT:=tAptDelay);
IF tonApt.Q THEN q_stSimulatedBPReadback.astApertures := stReqOutBP.astApertures; END_IF
tonRate(IN:=stReqOutBP.nRate<>q_stSimulatedBPReadback.nRate, PT:=tRateDelay);
IF tonRate.Q THEN q_stSimulatedBPReadback.nRate := stReqOutBP.nRate; END_IF
tonTrans(IN:=stReqOutBP.nTran<>q_stSimulatedBPReadback.nTran, PT:=tTransDelay);
IF tonTrans.Q THEN q_stSimulatedBPReadback.nTran := stReqOutBP.nTran; END_IF
tonPE(IN:=stReqOutBP.neVRange<>q_stSimulatedBPReadback.neVRange, PT:=tPEDelay);
IF tonPE.Q THEN
IF stReqOutBP.neVRange = 0 THEN
q_stSimulatedBPReadback.neVRange := 0;
q_stSimulatedBPReadback.neVRange.0 := TRUE;
ELSE
q_stSimulatedBPReadback.neVRange := stReqOutBP.neVRange;
END_IF
END_IF
tonBCRange(IN:=stReqOutBP.nBCRange<>q_stSimulatedBPReadback.nBCRange, PT:=tPEDelay);
IF tonBCRange.Q THEN
IF stReqOutBP.nBCRange = 0 THEN
q_stSimulatedBPReadback.nBCRange := 0;
ELSE
q_stSimulatedBPReadback.nBCRange := stReqOutBP.nBCRange;
END_IF
END_IF
tonSatt.IN := 0 <> MEMCMP(ADR(stReqOutBP.astAttenuators), ADR(q_stSimulatedBPReadback.astAttenuators), SIZEOF(q_stSimulatedBPReadback.astAttenuators));
tonSatt(PT:=tSattDelay);
IF tonSatt.Q THEN q_stSimulatedBPReadback.astAttenuators := stReqOutBP.astAttenuators; END_IF
END_IF
ffo.ExecuteNoLog();
IF NOT ffo.q_xFastFaultOut THEN q_stSimulatedBPReadback.nRate := 0; END_IF
END_METHOD
METHOD CheckRequest : BOOL
VAR_INPUT
nReqID : DWORD;
END_VAR
CheckRequest := nReqID = nSavedID AND (nReqID <> 0) AND (nReqID <> PMPS_GVL.EXCLUDED_ASSERTION_ID);
END_METHOD
// <Arbiter Internal>
// Elevates the arbitrated BP set to something above.
// Could be another arbiter, or a BP requester/ IO,
// or an FB that locks in a specific portion of the BP set.
METHOD ElevateRequest : BOOL
VAR_INPUT
HigherAuthority : I_HigherAuthority;
END_VAR
END_METHOD
METHOD RemoveRequest : BOOL
VAR_INPUT
(*StateID to remove*)
nReqID : DWORD;
END_VAR
nSavedID := 0;
stReqOutBP := stSaveCurrentBP;
RemoveRequest := TRUE;
END_METHOD
// Request a BP from this higher authority
METHOD RequestBP : BOOL
VAR_INPUT
(*StateID of state requesting beam parameter set*)
nReqID : DWORD;
(*Requested beam params*)
stReqBP : ST_BeamParams;
END_VAR
IF nReqID <> 0 AND nReqID <> PMPS_GVL.EXCLUDED_ASSERTION_ID THEN
nSavedID := nReqID;
stReqOutBP := stReqBP;
RequestBP := TRUE;
ELSE
RequestBP := FALSE;
END_IF
END_METHOD
PROPERTY AutoUpdateBP : BOOL
VAR
END_VAR
AutoUpdateBP := bAutoUpdateBP;
END_PROPERTY
PROPERTY AutoUpdateBP : BOOL
VAR
END_VAR
bAutoUpdateBP := AutoUpdateBP;
END_PROPERTY
PROPERTY nLowerAuthorityID : DWORD
VAR
END_VAR
nLowerAuthorityID := PMPS_GVL.EXCLUDED_ASSERTION_ID;
END_PROPERTY
- Related:
FB_DummyHA
FUNCTION_BLOCK FB_DummyHA IMPLEMENTS I_HigherAuthority, I_LowerAuthority
VAR_INPUT
ReqAcknowledged : BOOL := TRUE; // Use to control when the HA CHeckRequest Returns TRUE
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
END_FUNCTION_BLOCK
METHOD CheckRequest : BOOL
VAR_INPUT
nReqID : DWORD;
END_VAR
CheckRequest := ReqAcknowledged;
END_METHOD
// <Arbiter Internal>
// Elevates the arbitrated BP set to something above.
// Could be another arbiter, or a BP requester/ IO,
// or an FB that locks in a specific portion of the BP set.
METHOD ElevateRequest : BOOL
VAR_INPUT
HigherAuthority : I_HigherAuthority;
END_VAR
END_METHOD
METHOD RemoveRequest : BOOL
VAR_INPUT
(*StateID to remove*)
nReqID : DWORD;
END_VAR
RemoveRequest := TRUE;
END_METHOD
METHOD RequestBP : BOOL
VAR_INPUT
(*StateID of state requesting beam parameter set*)
nReqID : DWORD;
(*Requested beam params*)
stReqBP : ST_BeamParams;
END_VAR
RequestBP := TRUE;
END_METHOD
PROPERTY nLowerAuthorityID : DWORD
VAR
END_VAR
nLowerAuthorityID := PMPS_GVL.EXCLUDED_ASSERTION_ID;
END_PROPERTY
- Related:
FB_evRangeCalculator_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_evRangeCalculator_Test EXTENDS FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
VAR CONSTANT
RandomBoundary : INT := 2;
END_VAR
BasicFunction();
END_FUNCTION_BLOCK
{attribute 'no_check'}
METHOD BasicFunction
VAR_INPUT
END_VAR
VAR
{attribute 'displaymode' := 'binary'}
LastWord : DWORD := 2#0000_0000_0000_0000_0000_0000_0000_0010;
{attribute 'displaymode' := 'binary'}
Result : DWORD;
{attribute 'displaymode' := 'binary'}
TestEV : DWORD;
{attribute 'displaymode' := 'binary'}
tEvW : DWORD;
tEv : REAL;
fbStr : FB_FormatString;
MiscNumber : INT;
END_VAR
VAR CONSTANT
nLowerLimitBoundary : INT := 0;
RandomBoundary : INT := 2;
MNU : INT := 3; //RandomBoundary + 1
MND : INT := 1; //RandomBoundary - 1
END_VAR
Test('CheckRanges');
//Lower limit equal
tEvW.nLowerLimitBoundary := TRUE;
tEv := PMPS_GVL.g_areVBoundaries[nLowerLimitBoundary];
Result := F_eVRangeCalculator(tEv, 0);
AssertTrue(
Result = tEvW,
'Lower limit not inclusive');
// Lower range flags
tEvW.nLowerLimitBoundary := TRUE;
tEv := PMPS_GVL.g_areVBoundaries[nLowerLimitBoundary] / 2;
Result := F_eVRangeCalculator(tEv, 0);
AssertTrue(
Result = tEvW,
'eV below lower range should come back true');
//Mid range
tEvW := 0;
tEvW.RandomBoundary := TRUE;
//ev result that is smack dab in the middle of a range
tEv := PMPS_GVL.g_areVBoundaries[RandomBoundary] - ((PMPS_GVL.g_areVBoundaries[RandomBoundary] - PMPS_GVL.g_areVBoundaries[MND]) / 2);
Result := F_eVRangeCalculator(tEv, 0);
AssertTrue(
Result = tEvW,
'In range failed, whatever you did, it broke this really bad.');
//Out of range
tEvW := 0;
tEvW.RandomBoundary := TRUE;
//Ev result that is beyond the current range (midway into the next range up)
tEv := PMPS_GVL.g_areVBoundaries[MNU] - (PMPS_GVL.g_areVBoundaries[MNU]-PMPS_GVL.g_areVBoundaries[RandomBoundary])/2;
Result := F_eVRangeCalculator(tEv, 0);
AssertFalse(
Result = tEvW,
'Out of range failed, whatever you did, it broke this really bad.');
TEST_FINISHED();
Test('Negative eV failsafes');
//Negative eV
AssertTrue(
F_eVRangeCalculator(-300, 0) = 16#FFFF_FFFF,
'Working with antimatter? Negative eV should failsafe to FFFF.');
TEST_FINISHED();
Test('Out of range eV failsafes');
// eV too high
AssertTrue(
F_eVRangeCalculator(PMPS_GVL.g_areVBoundaries[PMPS_GVL.g_cBoundaries] + 1, 0) = 16#FFFF_FFFF,
'eV above the last threshold should failsafe.');
TEST_FINISHED();
Test('Check hyst keeps');
LastWord := F_eVRangeCalculator(PMPS_GVL.g_areVBoundaries[RandomBoundary], 2#0000_0000_0000_0000_0000_0000_0000_0000);
// Last word should be 2#0000_0000_0000_0100
Result := F_eVRangeCalculator(
PMPS_GVL.g_areVBoundaries[RandomBoundary] + PMPS_GVL.reVHyst - PMPS_GVL.reVHyst/2,
LastWord);
TestEV := 0;
TestEV.RandomBoundary := TRUE;
TestEV.MNU := True;
fbStr.arg1 := F_DWORD(Result);
fbStr.arg2 := F_DWORD(TestEV);
fbStr.sFormat := 'Moving up does not stick. Result: %b vs. Test: %b';
fbStr();
AssertTrue(Result = TestEV,
fbStr.sOut);
Result := F_eVRangeCalculator(
PMPS_GVL.g_areVBoundaries[RandomBoundary - 1] - PMPS_GVL.reVHyst + PMPS_GVL.reVHyst/2,
LastWord);
TestEV := 0;
TestEV.RandomBoundary := TRUE;
TestEV.MND := True;
fbStr.arg1 := F_DWORD(Result);
fbStr.arg2 := F_DWORD(TestEV);
fbStr.sFormat := 'Moving down does not stick. Result: %b vs. Test: %b';
fbStr();
AssertTrue(Result = TestEV,
fbStr.sOut);
TEST_FINISHED();
Test('Check hyst drops');
Result := F_eVRangeCalculator(
PMPS_GVL.g_areVBoundaries[MND] - PMPS_GVL.reVHyst*1.1,
LastWord);
TestEV := 0;
TestEV.MND := TRUE;
fbStr.arg1 := F_DWORD(Result);
fbStr.arg2 := F_DWORD(TestEV);
fbStr.sFormat := 'Moving down does not drop. Result: %b vs. Test: %b';
fbStr();
AssertTrue(Result = TestEV,
fbStr.sOut);
Result := F_eVRangeCalculator(
PMPS_GVL.g_areVBoundaries[RandomBoundary] + PMPS_GVL.reVHyst*1.1,
LastWord);
TestEV := 0;
TestEV.MNU := TRUE;
fbStr.arg1 := F_DWORD(Result);
fbStr.arg2 := F_DWORD(TestEV);
fbStr.sFormat := 'Moving up does not drop. Result: %b vs. Test: %b';
fbStr();
AssertTrue(Result = TestEV,
fbStr.sOut);
TEST_FINISHED();
END_METHOD
- Related:
FB_eVSimulator
(* eV Simulator
A. Wallace 2019-8-30
Adds noise to the eV and changes eV position occasionally.
*)
FUNCTION_BLOCK FB_eVSimulator
VAR_INPUT
NoiseLevel : REAL := 0; // eV Noise
ChangeTime : TIME := T#10S;
END_VAR
VAR_OUTPUT
eV : REAL := 300;
END_VAR
VAR
eVRange : REAL := 1600;
timer: TON;
eVRand : DRAND :=(Seed:=0);
NoiseRand : DRAND := (Seed:=0);
END_VAR
timer(in:=TRUE, PT:=ChangeTime);
//Occasionally change eV
IF timer.Q THEN
timer(in:=FALSE);
eVRand();
eV := LIMIT(0, eVRange*LREAL_TO_REAL(eVRand.Num), eVRange);
END_IF
// Noise generation
NoiseRand();
eV := eV + NoiseLevel*LREAL_TO_REAL(NoiseRand.Num);
NoiseRand();
eV := LIMIT(0, ev - NoiseLevel*LREAL_TO_REAL(NoiseRand.Num), eVRange);
END_FUNCTION_BLOCK
FB_eVWithinSpec_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_eVWithinSpec_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
evWithinRangeChecks();
END_FUNCTION_BLOCK
METHOD evWithinRangeChecks
VAR_INPUT
END_VAR
VAR
END_VAR
TEST('evWithinRangeChecks');
//Upper limit equal
AssertTrue(
F_eVWithinSpec(300, 2#0000_0000_0000_0010),
'Upper limit not inclusive');
//Lower limit equal
AssertTrue(
F_eVWithinSpec(300, 2#0000_0000_0000_0100),
'Lower limit not inclusive');
//Out of range
AssertFalse(
F_eVWithinSpec(300.1, 2#0000_0000_0000_0010),
'Out of range failed, whatever you did, it broke this really bad.');
//Negative eV
AssertFalse(
F_eVWithinSpec(-300, 16#FFFF),
'Working with antimatter? Negative eV should not be acceptable.');
//Upper unallocated range
AssertFalse(
F_eVWithinSpec(1701, 2#1000_0000_0000_0000),
'eV range should not evaluate past 1.7keV');
TEST_FINISHED();
END_METHOD
- Related:
FB_FastFault
(* Fast Fault
2019-9-13 A. Wallace
Use this block to generate a beam-off fault. Connects to a fast fault hardware output
function block to contribute to the state of the fast fault output (FFO).
If the i_xOK goes false, the associated FFO will go false, despite the state of any other
contributing fast faults, unless the FFO is currently vetoed.
*)
{attribute 'reflection'}
FUNCTION_BLOCK FB_FastFault
VAR_INPUT
i_xOK : BOOL; // Connect to fast-fault condition (false produces fault)
i_xReset : BOOL; // Resets when i_xOK is true and this is true
i_xAutoReset : BOOL := FALSE; // Automatically clear fast fault (latching vs non-latching)
i_xVetoable : BOOL := TRUE; // Mask this fast fault if the FFO veto device is true
i_DevName : T_MaxString := ''; // Device name for diagnostic
i_Desc : T_MaxString := ''; // Description of fast fault (you should set at init)
i_TypeCode : UINT; // Error code for classifying fast faults
END_VAR
VAR_OUTPUT
o_xFFLine : BOOL; //Connect to HW output or another FF input if you like (Optional)
END_VAR
VAR_IN_OUT
io_fbFFHWO : FB_HardwareFFOutput; //Point to FB_HardwareFFOutput of your choice
END_VAR
VAR
{attribute 'instance-path'}
{attribute 'noinit'}
sPath : T_MaxString;
FFInfo : ST_FFInfo;
RegistrationIdx : UINT := 1; // The index this FF was registered in the FFO
xInit : BOOL :=TRUE;
InfoStringFmtr : FB_FormatString;
InUse : T_MaxString;
AutoReset : T_MaxString;
END_VAR
IF xInit THEN
FFInfo.sPath := sPath;
FFInfo.InUse := True;
FFInfo.TypeCode := i_TypeCode;
FFInfo.DevName := i_DevName;
FFInfo.Desc := i_Desc;
FFInfo.AutoReset := i_xAutoReset;
FFInfo.Vetoable := i_xVetoable;
InUse := BOOL_TO_STRING(FFInfo.InUse);
AutoReset := BOOL_TO_STRING(FFInfo.AutoReset);
InfoStringFmtr(sFormat:='%s;%s;%X;%s;%s;%s;',
arg1 := F_STRING(FFinfo.sPath),
arg2 := F_STRING(InUse),
arg3 := F_WORD(FFInfo.TypeCode),
arg4 := F_STRING(FFInfo.DevName),
arg5 := F_STRING(FFInfo.Desc),
arg6 := F_STRING(AutoReset),
sOut=>FFInfo.InfoString);
io_fbFFHWO.Register(
stFFInfo:=FFInfo,
Idx=>RegistrationIdx);
//<TODO> if registration doesn't succeed, send warning to diagnostic
xInit := FALSE;
END_IF
io_fbFFHWO.IdxCheckIn(Idx:=RegistrationIdx, OK := i_xOK, Reset:= i_xReset);
END_FUNCTION_BLOCK
- Related:
FB_FastFault_Test
{attribute 'call_after_init'}
{attribute 'no_check'}
FUNCTION_BLOCK FB_FastFault_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
fbTime : FB_LocalSystemTime := ( bEnable := TRUE, dwCycle := 1 ); //Get current system time, used for override
END_VAR
VAR CONSTANT
OvrdTime : TIME := T#2s;
END_VAR
FFCombinedFunctionality();
FFRegistration();
FFOvrdAndNextFault();
END_FUNCTION_BLOCK
METHOD FFCombinedFunctionality
VAR_INPUT
END_VAR
VAR_INST
fbFF : FB_FastFault;
fbFFO : FB_HardwareFFOutput;
END_VAR
TEST('FFCombinedFunctionality');
//Clear initial faults
fbFFO.i_xReset:=TRUE; //Reset requested at start of cycle
fbFF(io_fbFFHWO := fbFFO, //FF reset somehwere in code
i_xOK := TRUE,
i_xReset := TRUE);
fbFFO.EvaluateOutput(); //FFO eval called at end of cycle
AssertTrue(fbFFO.q_xFastFaultOut,
'Fast fault did not clear');
//Induce fault with FF
fbFFO(i_xReset := FALSE); //Reset released at start of cycle
fbFF(io_fbFFHWO := fbFFO, //FF faulted
i_xOK := FALSE); //Reset is still set true from last call.
fbFFO.EvaluateOutput(); //FFO eval called at end of cycle
AssertFalse(fbFFO.q_xFastFaultOut,
'Fast fault did not trip the beam');
//FFO remains faulted until local (FF) and FFO receieve fresh reset request, and faults
//are actually cleared
//Attempt to clear FFO fails because FF still faulted
fbFFO.i_xReset := TRUE; //Reset requested
fbFF(io_fbFFHWO := fbFFO, //FF faulted
i_xOK := FALSE,
i_xReset := FALSE);
fbFFO.EvaluateOutput(); //FFO eval called at end of cycle
AssertFalse(fbFFO.q_xFastFaultOut,
'Fast fault output cleared while fault remains, very bad');
//Attempt to clear FF while fault remains fails
fbFFO.EvaluateOutput(i_xReset := FALSE);
fbFFO.i_xReset := TRUE; //Reset requested
fbFF(io_fbFFHWO := fbFFO, //FF faulted
i_xOK := FALSE,
i_xReset := TRUE); //This reset should fail.
fbFFO.EvaluateOutput(); //FFO eval called at end of cycle
AssertFalse(fbFFO.q_xFastFaultOut,
'Fast fault cleared while fault remains, very bad');
TEST_FINISHED();
END_METHOD
{attribute 'no_check'}
METHOD FFOvrdAndNextFault
VAR_INPUT
END_VAR
VAR_INST
fbFF : FB_FastFault :=(
i_xAutoReset := TRUE);
fbFFO : FB_HardwareFFOutput := (i_sNetID:='',
bAutoReset := TRUE);
rFF : REFERENCE TO ST_FF;
FirstPass : BOOL := TRUE;
fbTimePass : BOOL := TRUE;
OvrdActvTstDone : BOOL := FALSE;
TestsDone : BOOL := FALSE;
Now: DATE_AND_TIME;
Expiration : DINT;
OvdTime : TIME ;
Expire: TIMESTRUCT;
//seconds:ULINT;
specificLocalTimeToFileTime : FB_TzSpecificLocalTimeToFileTime;
fileTime: T_FILETIME;
END_VAR
TEST('FFVetoAndNextFault');
fbTime();
// I noticed something weird going on with the PEWatcher on the L line. It was faulted, and the fast fault
// was not tripping off beam. It had been previously overridden so I was wondering why it wasn't causing a trip.
IF FirstPass THEN
fbFF(io_fbFFHWO := fbFFO, //FF reset somehwere in code
i_xOK := TRUE);
fbFFO.ExecuteNoLog(); //FFO eval called at end of cycle
AssertTrue(fbFFO.q_xFastFaultOut,
'Fast fault did not clear');
//Induce fault with FF
fbFF(io_fbFFHWO := fbFFO, //FF faulted
i_xOK := FALSE);
fbFFO.ExecuteNoLog(); //FFO eval called at end of cycle
AssertFalse(fbFFO.q_xFastFaultOut,
'Fast fault did not trip the beam');
rFF REF= fbFFO.astFF[fbFF.RegistrationIdx];
FirstPass := FALSE;
END_IF
IF fbTime.bValid AND fbTimePass THEN
Now := SystemTime_TO_DT(fbTime.systemTime);
Expire:= fbTime.systemTime;
Expire.wSecond:= Expire.wSecond+5;
specificLocalTimeToFileTime(in := Tc2_Utilities.SYSTEMTIME_TO_FILETIME(Expire), tzInfo := , out => fileTime);
Expiration := TO_DINT((SHL(DWORD_TO_ULINT(fileTime.dwHighDateTime), 32) + DWORD_TO_ULINT(fileTime.dwLowDateTime)) / 10000000 - 11644473600);;
WRITE_PROTECTED_DINT(ADR(rFF.Ovrd.Expiration), Expiration );
WRITE_PROTECTED_TIME(ADR(rFF.Ovrd.Timer.PT), OvrdTime );
WRITE_PROTECTED_BOOL(ADR(rFF.Ovrd.Activate), TRUE);
fbTimePass:=FALSE;
END_IF
// On every cycle:
fbFF(io_fbFFHWO := fbFFO, //FF faulted
i_xOK := FALSE);
fbFFO.ExecuteNoLog(); //FFO eval called at end of cycle
IF rFF.Ovrd.Active AND rFF.BeamPermitted THEN
AssertTrue(fbFFO.q_xFastFaultOut,
'Fast fault should be overridden so FFO should be true');
OvrdActvTstDone := TRUE;
END_IF
IF OvrdActvTstDone and NOT rFF.Ovrd.Active and not rff.BeamPermitted THEN
AssertFalse(fbFFO.q_xFastFaultOut,
'Fast fault override expired so beam should be off.');
// AssertFalse(rFF.BeamPermitted, 'Beam should not be permitted now');
TestsDone := TRUE;
END_IF
IF TestsDone THEN
TEST_FINISHED_NAMED('FFVetoAndNextFault');
END_IF
END_METHOD
{attribute 'no_check'}
METHOD FFRegistration
VAR_INPUT
END_VAR
VAR_INST
fbFF : FB_FastFault;
fbFFO : FB_HardwareFFOutput;
astFF : ARRAY[0..10] OF ST_FF;
END_VAR
TEST('FFRegistration');
fbFF(io_fbFFHWO := fbFFO);
AssertEquals_STRING(fbFF.sPath, fbFFO.astFF[fbFF.RegistrationIdx].Info.sPath,
'FF registration with FFO did not succeed');
TEST_FINISHED();
END_METHOD
- Related:
FB_HardwareFFOutput
{attribute 'reflection'}
{attribute 'no_check'}
FUNCTION_BLOCK FB_HardwareFFOutput
VAR CONSTANT
FF_ARRAY_UPPER_BOUND : UINT := PMPS_PARAM.MAX_FAST_FAULTS;
END_VAR
VAR_INPUT
{attribute 'pytmc' := '
pv: ClearFault
io: o
field: DESC Might be overidden by PLC writes
'}
i_xReset : BOOL;
{attribute 'pytmc' := '
pv: EnableVeto
io: o
'}
i_xVeto : BOOL;
bAutoReset : BOOL := FALSE; // Set true for the FFO to automatically permit beam again after all fast faults are cleared
i_sNetID : T_AmsNetID:=''; //Set to the Arbiter AmsNetID to be used for the synchronisation. An empty string means the system will sue local time
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: FaultHWO
io: i
field: DESC Hardware Output Status
'}
q_xFastFaultOut AT %Q* : BOOL;
q_xValidSyncTime : BOOL;// system time bValid output True when sync is successful
END_VAR
VAR_IN_OUT
END_VAR
VAR
{attribute 'pytmc' := '
pv: FF
'}
astFF : ARRAY[1..FF_ARRAY_UPPER_BOUND] OF ST_FF;
{attribute 'pytmc' := '
pv: RegistrationFailure
io: io
'}
xFastFaultRegFail : BOOL := FALSE; // Set true if a fast fault fails to register. Holds beam off.
tFFRegFail : F_TRIG;
{attribute 'instance-path'}
{attribute 'noinit'}
sPath : T_MaxString;
{attribute 'pytmc' := '
pv: OK
io: i
'}
xOK : BOOL:= TRUE; // Current internal state of FFO, indicates if FFO will accept a reset
rtReset : R_TRIG;
rtResetandOK : R_TRIG;
nIndex : UINT := 1;
IdxOK: BOOL;
fbTime : FB_LocalSystemTime := ( bEnable := TRUE, dwCycle := 1 ); //Get current system time, used for override
fbTime_to_UTC: FB_TzSpecificLocalTimeToSystemTime;
fbGetTimeZone: FB_GetTimeZoneInformation;
fbJson : FB_JsonSaxWriter;
pmpsTypeCode : UDINT := 0; // shows up in json as pmps_typecode
fbLogger : FB_LogMessage := (
eSevr := TcEventSeverity.Critical,
eSubsystem := E_Subsystem.MPS,
nMinTimeViolationAcceptable := PMPS_PARAM.MAX_FAST_FAULTS);
END_VAR
// <TODO> latch off beam if DI card status goes bad
END_FUNCTION_BLOCK
ACTION EvaluateOutput:
////////////////////////////////////////////////////////////////
// Critical code. Do not touch unless you know what you're doing
////////////////////////////////////////////////////////////////
//<TODO> add diagnostic for success or failure of reset
rtReset(CLK:=i_xReset);
rtResetandOK(CLK:=(rtReset.Q OR bAutoReset) AND xOK);
q_xFastFaultOut S= rtResetandOK.Q;
q_xFastFaultOut R= (NOT xOK OR xFastFaultRegFail);
//Reset OK for next cycle
xOK := True;
////////////////////////////////////////////////////////////////
END_ACTION
ACTION Execute:
EvaluateOverrides();
EvaluateOutput();
ExecuteLogging();
END_ACTION
ACTION ExecuteNoLog:
EvaluateOverrides();
EvaluateOutput();
//ExecuteLogging();
END_ACTION
{attribute 'no_check'}
METHOD EvaluateOverrides : BOOL
VAR_INPUT
END_VAR
VAR
FF : REFERENCE TO ST_FF;
EvalIdx: DINT := 1;
END_VAR
VAR CONSTANT
MaxTime : DINT:= 4294080;(*49.7 days*)
END_VAR
//Get local System Time
fbTime(sNetID:=i_sNetID);
//Get Time Zone
fbGetTimeZone(sNetID:=i_sNetID,bExecute:=TRUE,tTimeout:=T#10S);
//change local time to UTC to be compatible with unix time epoch widget
fbTime_to_UTC(in:= fbTime.systemTime , tzInfo:=fbGetTimeZone.tzInfo);
q_xValidSyncTime := fbTime.bValid AND NOT fbGetTimeZone.bError;
FOR EvalIdx := 1 TO FF_ARRAY_UPPER_BOUND DO
FF REF= astFF[EvalIdx];
IF NOT FF.Info.InUse THEN
CONTINUE;
ELSE
// Veto timer
IF FF.Ovrd.Deactivate THEN
FF.Ovrd.Timer.PT := T#0s; // Only needs to be set zero for one cycle
ELSIF FF.Ovrd.Activate THEN
FF.Ovrd.StartDT:= TO_DINT(TO_DT(SystemTime_TO_DT(fbTime_to_UTC.out)));
IF (FF.Ovrd.StartDT < FF.Ovrd.Expiration) THEN
FF.Ovrd.Expiration:= LIMIT(0,FF.Ovrd.Expiration, FF.Ovrd.StartDT+MaxTime );
FF.Ovrd.Timer.PT := TO_DT(FF.Ovrd.Expiration)- SystemTime_TO_DT(fbTime_to_UTC.out);
END_IF
END_IF
// UDINT conversions for ESS module compatibility.
FF.Ovrd.Timer(
IN := FF.Ovrd.Activate, // Rising edge activated
Q => FF.Ovrd.Active);
FF.Ovrd.tOvrdActivate(CLK:=FF.Ovrd.Active);
FF.Ovrd.OvrdActLogAck S= FF.Ovrd.tOvrdActivate.Q;
FF.Ovrd.tOvrdExpiring(CLK:=FF.Ovrd.Active);
FF.Ovrd.OvrdExpLogAck S= FF.Ovrd.tOvrdExpiring.Q;
FF.Ovrd.ElapsedTime := TIME_TO_UDINT(FF.Ovrd.Timer.ET);
FF.Ovrd.RemainingTime := MAX(0, TO_DINT(FF.Ovrd.Timer.PT) - FF.Ovrd.ElapsedTime);
// Clear pushbuttons
FF.Ovrd.Activate := FALSE;
FF.Ovrd.Deactivate := FALSE;
END_IF
END_FOR
END_METHOD
{attribute 'obsolete' := 'Use EvaluateOverrides instead.'}
METHOD EvaluateVetos : BOOL
VAR_INPUT
END_VAR
END_METHOD
{attribute 'no_check'}
METHOD ExecuteLogging : BOOL
VAR_INPUT
END_VAR
VAR
FF : REFERENCE TO ST_FF;
logIdx : DINT := 1;
END_VAR
VAR_INST
HelloTimer : TOF := ( PT:=T#24h );
END_VAR
FOR logIdx := 1 to FF_ARRAY_UPPER_BOUND do
FF REF= astFF[logIdx];
IF NOT FF.Info.InUse THEN
CONTINUE;
ELSE
IF FF.FaultAck THEN
pmpsTypeCode := 1;
FormulateLogJson(FF);
fbLogger(sMsg := 'Fault, beam off', eSevr:=TcEventSeverity.Critical);
FF.FaultAck := FALSE;
END_IF
IF FF.Ovrd.OvrdActLogAck THEN
pmpsTypeCode := 2;
FormulateLogJson(FF);
fbLogger(sMsg := 'Override activated', eSevr:=TcEventSeverity.Warning);
FF.Ovrd.OvrdActLogAck := FALSE;
END_IF
IF FF.Ovrd.OvrdExpLogAck THEN
pmpsTypeCode := 3;
FormulateLogJson(FF);
fbLogger(sMsg := 'Override released', eSevr:=TcEventSeverity.Warning);
FF.Ovrd.OvrdExpLogAck := FALSE;
END_IF
IF FF.ClearAck THEN
pmpsTypeCode := 8;
FormulateLogJson(FF);
fbLogger(sMsg := 'Fault cleared', eSevr:=TcEventSeverity.Info);
FF.ClearAck := FALSE;
END_IF
END_IF
END_FOR
// Log registration fault cleared
tFFRegFail(CLK:=xFastFaultRegFail);
IF tFFRegFail.Q THEN
fbJson.StartObject();
fbJson.AddKey('pmps_typecode');
fbJson.AddUdint(5);
fbJson.AddKey('pmps_path');
fbJson.AddString(sPath);
fbJson.EndObject();
fbJson.CopyDocument(pDoc:=fbLogger.sJson, SIZEOF(fbLogger.sJson));
fbLogger(sMsg := 'Fast fault registration failure cleared. This may be bad as a fast fault could go unoticed.',
eSevr:=TcEventSeverity.Warning);
END_IF
// Say hello
HelloTimer.IN := NOT HelloTimer.Q;
HelloTimer();
IF HelloTimer.IN THEN
pmpsTypeCode := 10;
FormulateLogJson(FF);
fbLogger(eSubsystem:=E_Subsystem.NILVALUE,
eSevr := TcEventSeverity.Info,
sMsg := 'FFO is alive');
fbLogger.eSubsystem := E_Subsystem.MPS;
END_IF
END_METHOD
METHOD INTERNAL FormulateLogJson : STRING
VAR_INPUT
FF : ST_FF;
END_VAR
fbJson.StartObject();
fbJson.AddKey('pmps_typecode');
fbJson.AddUdint(pmpsTypeCode);
fbJson.AddKey('pmps_path');
fbJson.AddString(FF.Info.sPath);
fbJson.AddKey('pmps_device_name');
fbJson.AddString(FF.Info.DevName);
fbJson.EndObject();
fbLogger.sJson := fbJson.GetDocument();
fbJson.ResetDocument();
END_METHOD
{attribute 'no_check'}
METHOD INTERNAL IdxCheckIn : BOOL
VAR_INPUT
Idx : DINT;
OK : BOOL;
Reset : BOOL;
END_VAR
VAR
stFF : ST_FF;
BeamPermitted : BOOL;
END_VAR
Idx := LIMIT(1,Idx,FF_ARRAY_UPPER_BOUND);
stFF := THIS^.astFF[Idx];
// Updating internal fault state
stFF.OK := OK;
// Reset and latching logic
stFF.rtReset(CLK:=stFF.Reset (*epics*) OR Reset (*from other PLC logic *) );
stFF.Reset R= stFF.rtReset.Q;
stFF.bsFF(SET := stFF.rtReset.Q OR stFF.Info.AutoReset, RESET1:= NOT OK);
BeamPermitted := stFF.bsFF.Q1 OR stFF.Ovrd.Active OR (i_xVeto AND stFF.Info.Vetoable);
// Fault generation
IF NOT BeamPermitted THEN
THIS^.xOK := FALSE; //Very clever, thanks Zach!
END_IF
// Fast fault accumulation
stFF.ftCountFault(CLK:=stFF.bsFF.Q1);
IF stFF.ftCountFault.Q THEN PMPS_GVL.AccumulatedFF := PMPS_GVL.AccumulatedFF + 1; END_IF
//Clear pushbuttons
stFF.Reset := FALSE;
// Log when this fault faults
stFF.FaultAck S= stFF.BeamPermitted AND NOT BeamPermitted;
// Log when this fault has cleared
stFF.ClearAck S= BeamPermitted AND NOT stFF.BeamPermitted;
stFF.BeamPermitted := BeamPermitted;
// Copy state back to ff struct
THIS^.astFF[Idx] := stFF;
END_METHOD
{attribute 'no_check'}
METHOD INTERNAL Register : BOOL
VAR_INPUT
stFFInfo : ST_FFInfo := (
sPath := '',
Desc := '',
TypeCode := 16#00,
DevName := ''); // Fast fault information
END_VAR
VAR_OUTPUT
FFOName : T_MaxString;
Idx : UINT;
END_VAR
VAR
END_VAR
FFOName := THIS^.sPath;
Idx := THIS^.nIndex;
IF THIS^.nIndex <= FF_ARRAY_UPPER_BOUND THEN
THIS^.astFF[MIN(THIS^.nIndex,FF_ARRAY_UPPER_BOUND)].Info:=stFFInfo;
THIS^.nIndex := THIS^.nIndex + 1;
Register := TRUE; // Add successful
ELSE
fbJson.StartObject();
fbJson.AddKey('pmps_typecode');
fbJson.AddUdint(4);
fbJson.AddKey('pmps_path');
fbJson.AddString(stFFInfo.sPath);
fbJson.AddKey('pmps_device_name');
fbJson.AddString(stFFInfo.DevName);
fbJson.EndObject();
fbLogger.sJson := fbJson.GetDocument();
fbJson.ResetDocument();
fbLogger(sMsg:='Fast fault registration failed. Too many fast faults on this FFO.',
eSevr:=TcEventSeverity.Warning,
eSubsystem:=E_Subsystem.MPS);
xFastFaultRegFail := TRUE;
Register := FALSE; // Failed to add name to list
END_IF
END_METHOD
- Related:
FB_Hgvpu
FUNCTION_BLOCK FB_Hgvpu IMPLEMENTS I_UndulatorComplex
VAR_INPUT
fbElectronEnergy : REFERENCE TO FB_LREALFromEPICS;
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: CurrentPhotonEnergy
io: i
field: DESC Calculated current photon energy
field: PREC 3
field: EGU eV
'}
fCurrentPhotonEnergy : LREAL;
{attribute 'pytmc' := '
pv: TargetPhotonEnergy
io: i
field: DESC Calculated desired photon energy
field: PREC 3
field: EGU eV
'}
fTargetPhotonEnergy : LREAL;
{attribute 'pytmc' := '
pv: SeedUndulatorNumber
io: i
field: DESC Seed undulator number
'}
nSeedUndulator : UDINT; // Set to zero when no undulators are active
{attribute 'pytmc' := '
pv: TargetSeedUndulatorNumber
io: i
field: DESC Seed undulator number for target K
'}
nTargetSeedUndulator : UDINT; // Set to zero when no undulators are active
END_VAR
VAR
// From lcls-srv01: grep -e KDes /u1/lcls/epics/ioc/data/ioc-undh-uc*/iocInfo/IOC.pvlist |sort
{attribute 'pytmc' := 'pv: 24; link: 2450:'}
fbSegment_24 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 25; link: 2550:'}
fbSegment_25 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 26; link: 2650:'}
fbSegment_26 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 27; link: 2750:'}
fbSegment_27 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 29; link: 2950:'}
fbSegment_29 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 30; link: 3050:'}
fbSegment_30 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 31; link: 3150:'}
fbSegment_31 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 32; link: 3250:'}
fbSegment_32 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 33; link: 3350:'}
fbSegment_33 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 34; link: 3450:'}
fbSegment_34 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 35; link: 3550:'}
fbSegment_35 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 36; link: 3650:'}
fbSegment_36 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 37; link: 3750:'}
fbSegment_37 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 38; link: 3850:'}
fbSegment_38 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 39; link: 3950:'}
fbSegment_39 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 40; link: 4050:'}
fbSegment_40 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 41; link: 4150:'}
fbSegment_41 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 42; link: 4250:'}
fbSegment_42 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 43; link: 4350:'}
fbSegment_43 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 44; link: 4450:'}
fbSegment_44 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 45; link: 4550:'}
fbSegment_45 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 46; link: 4650:'}
fbSegment_46 : FB_UndulatorSegment;
fbSegment : ARRAY [iLowBound_LUnd..iHighBound_LUnd] OF POINTER TO FB_UndulatorSegment;
fbCurrentSegment : REFERENCE TO FB_UndulatorSegment;
iIndex : UDINT;
bInitialized : BOOL := FALSE;
END_VAR
VAR CONSTANT
{attribute 'pytmc' := '
pv: FirstSegment
io: i
'}
iLowBound_LUnd : UDINT := 24;
{attribute 'pytmc' := '
pv: LastSegment
io: i
'}
iHighBound_LUnd : UDINT := 46;
{attribute 'pytmc' := '
pv: Period
io: i
field: EGU mm
'}
fPeriod_mm_LUnd : LREAL := 26.0;
{attribute 'pytmc' := '
pv: LowK
io: i
'}
fLowK_LUnd : LREAL := 0.54;
{attribute 'pytmc' := '
pv: HiK
io: i
'}
fHiK_LUnd : LREAL := 2.6;
END_VAR
IF NOT bInitialized THEN
Init();
END_IF
UndAdrUpdate();
nSeedUndulator := 0;
nTargetSeedUndulator := 0;
FOR iIndex := iLowBound_LUnd TO iHighBound_LUnd DO
IF fbSegment[iIndex] <> 0 THEN
fbCurrentSegment REF= fbSegment[iIndex]^;
fbCurrentSegment(fbElectronEnergy:=fbElectronEnergy);
//Mark the seed undulator, first undulator operating within K bounds
IF fbCurrentSegment.xActive AND nSeedUndulator = 0 THEN
nSeedUndulator := iIndex;
fCurrentPhotonEnergy := fbCurrentSegment.fPhotonEnergyAct;
END_IF
IF fbCurrentSegment.xTargetActive AND nTargetSeedUndulator = 0 THEN
nTargetSeedUndulator := iIndex;
fTargetPhotonEnergy := fbCurrentSegment.fPhotonEnergyDes;
END_IF
END_IF
END_FOR
IF nSeedUndulator = 0 THEN
fCurrentPhotonEnergy := 0;
END_IF
IF nTargetSeedUndulator = 0 THEN
fTargetPhotonEnergy := 0;
END_IF
END_FUNCTION_BLOCK
ACTION Init:
UndAdrUpdate();
FOR iIndex := iLowBound_LUnd TO iHighBound_LUnd DO
IF fbSegment[iIndex] <> 0 THEN
fbCurrentSegment REF= fbSegment[iIndex]^;
fbCurrentSegment.fPeriod_mm := fPeriod_mm_LUnd;
fbCurrentSegment.fLowK := fLowK_LUnd;
fbCurrentSegment.fHiK := fHiK_LUnd;
END_IF
END_FOR
bInitialized := TRUE;
END_ACTION
ACTION UndAdrUpdate:
fbSegment[24] := ADR(fbSegment_24);
fbSegment[25] := ADR(fbSegment_25);
fbSegment[26] := ADR(fbSegment_26);
fbSegment[27] := ADR(fbSegment_27);
fbSegment[28] := 0;
fbSegment[29] := ADR(fbSegment_29);
fbSegment[30] := ADR(fbSegment_30);
fbSegment[31] := ADR(fbSegment_31);
fbSegment[32] := ADR(fbSegment_32);
fbSegment[33] := ADR(fbSegment_33);
fbSegment[34] := ADR(fbSegment_34);
fbSegment[35] := ADR(fbSegment_35);
fbSegment[36] := ADR(fbSegment_36);
fbSegment[37] := ADR(fbSegment_37);
fbSegment[38] := ADR(fbSegment_38);
fbSegment[39] := ADR(fbSegment_39);
fbSegment[40] := ADR(fbSegment_40);
fbSegment[41] := ADR(fbSegment_41);
fbSegment[42] := ADR(fbSegment_42);
fbSegment[43] := ADR(fbSegment_43);
fbSegment[44] := ADR(fbSegment_44);
fbSegment[45] := ADR(fbSegment_45);
fbSegment[46] := ADR(fbSegment_46);
END_ACTION
PROPERTY rCurrentPhotonEnergy : REAL
VAR
END_VAR
rCurrentPhotonEnergy := fCurrentPhotonEnergy;
END_PROPERTY
PROPERTY rTargetPhotonEnergy : REAL
VAR
END_VAR
rTargetPhotonEnergy := fTargetPhotonEnergy;
END_PROPERTY
- Related:
FB_JsonDocToSafeBP
FUNCTION_BLOCK FB_JsonDocToSafeBP
VAR_INPUT
bExecute : BOOL; //Rising Edge
jsonDoc : SJsonValue;
sDeviceName : STRING;
END_VAR
VAR_OUTPUT
bHasDevice : BOOL;
bHasAllStates : BOOL;
bHasAllParameters :BOOL := TRUE;
bBusy : BOOL;
bError : BOOL;
nErrId : UDINT;
sErrMsg :STRING;
END_VAR
VAR_IN_OUT
//ARRAY [1.. MOTION_GVL.MAX_STATES] ;
arrStates: ARRAY[*] OF ST_DbStateParams;
io_fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR
(* JSON *)
fbJson : FB_JsonDomParser;
jsonProp : SJsonValue;
jsonValue : SJsonValue;
jsonParam : SJsonValue;
jsonnTran : SJsonValue;
jsonnRate : SJsonValue;
Step: INT;
index: DINT;
nStateCount:DINT;
RisingEdge : R_TRIG;
(*Logger*)
tNewMessage : R_TRIG;
fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.MPS, nMinTimeViolationAcceptable:=10);
(*FFO*)
FFO : FB_FastFault :=(
i_Desc := 'Fault occurs when there is an error loading safe beam parameters from json file',
i_TypeCode := 16#FF13);
sbuffReadSmall: INT;
END_VAR
(* Retrieve JSON content *)
RisingEdge(CLK:=bExecute);
CASE Step OF
0: (* Idle state *)
IF RisingEdge.Q THEN
bBusy := TRUE;
bError:= FALSE;
nErrId:=0;
sErrMsg :='';
Step := 20;
fbLogger(sMsg:='Start Parsing Json DOC.', eSevr:=TcEventSeverity.Info);
END_IF
20: (*Find the Device Name*)
bHasDevice := fbJson.HasMember(jsonDoc, sDeviceName);
IF (bHasDevice) THEN
jsonProp := fbJson.FindMember(jsonDoc, sDeviceName);
IF jsonProp <> 0 THEN
Step := Step+1;
ELSE
Step := 900 + Step;
nErrId := Step;
bError := TRUE;
sErrMsg:= CONCAT('Error device name not found in Json File : ',sDeviceName);
END_IF
ELSE
Step := 900 + Step;
nErrId := Step;
bError := TRUE;
sErrMsg:= CONCAT('Error device name not found in Json File : ',sDeviceName);
END_IF
21: (*Check device States in DOM*)
nStateCount :=0;
FOR Index:=LOWER_BOUND(arrStates,1) TO UPPER_BOUND(arrStates,1) BY 1 DO
IF NOT ((arrStates[Index].sPmpsState= '') OR (arrStates[Index].sPmpsState= 'Invalid')) THEN
IF NOT (fbJson.HasMember(jsonProp, arrStates[Index].sPmpsState)) THEN
Step := 900+ Step;
nErrId := Step;
bError := TRUE;
sErrMsg:= CONCAT('Error loading device state in Json File : ',arrStates[Index].sPmpsState);
fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical);
EXIT;
ELSE
nStateCount :=nStateCount+1;
END_IF
ELSE
bHasAllStates :=TRUE;
EXIT;
END_IF
END_FOR
Step := Step+1;
22: //Make sure state array input has State name data
IF (nStateCount =0) THEN
bHasAllStates :=FALSE;
Step := 900+ Step;
nErrId := Step;
bError := TRUE;
sErrMsg:= CONCAT('Error Input State array is empty : ',sDeviceName);
ELSE
Step := Step+1;
END_IF;
23: (*loop Device State Arrays to find load all States BP*)
FOR Index:=LOWER_BOUND(arrStates,1) TO UPPER_BOUND(arrStates,1) BY 1 DO
IF NOT (arrStates[Index].sPmpsState= '') THEN
IF(NOT M_LoadSafeBP(arrStates[Index].sPmpsState, Index)) THEN
Step := 900+ Step;
fbLogger.sMsg := CONCAT('Error Parsing beam parameters - Device/States: ', arrStates[Index].sPmpsState);
fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical);
END_IF
ELSE
EXIT;
END_IF
END_FOR
IF (bHasAllParameters) THEN Step := 30;
ELSE Step := 900+ Step;
END_IF
30: (*ALL good here*)
fbLogger.sMsg := CONCAT('Complete Parsing Json Doc. Device: ', sDeviceName);
fbLogger.sMsg := CONCAT(fbLogger.sMsg ,' - States Count: ');
fbLogger.sMsg := CONCAT(fbLogger.sMsg ,TO_STRING(nStateCount));
fbLogger(eSevr:=TcEventSeverity.Info);
bExecute:= FALSE;
bBusy:=FALSE;
Step := 0;
FFO.i_xOK := TRUE;
END_CASE
//Fault in error state
IF(Step>=900) THEN
FFO.i_xOK := FALSE;
bBusy:=FALSE;
Step:=0;
END_IF
ACT_FFO();
ACT_LOGGER();
END_FUNCTION_BLOCK
ACTION ACT_FFO:
(*FAST FAULT*)
FFO(i_xOK := ,
i_xReset := ,
i_xAutoReset :=TRUE,
i_DevName := sDeviceName,
io_fbFFHWO := io_fbFFHWO);
END_ACTION
ACTION ACT_Logger:
// Log valve open
tNewMessage(CLK:= NOT(FbLogger.sMsg = sErrMsg) AND NOT (sErrMsg =''));
IF tNewMessage.Q THEN fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION
METHOD M_LoadSafeBP : BOOL
VAR_INPUT
sStateName : STRING;
Index:DINT;
END_VAR
VAR
//bHasAllParameters : BOOL := TRUE;
sAperture:STRING;
nAperture:INT := -1;
sEV:STRING;
sBC:STRING;
nIndex:INT;
END_VAR
bHasAllParameters := fbJson.HasMember(jsonProp, sStateName);
jsonValue := fbJson.FindMember(jsonProp, sStateName);
IF bHasAllParameters THEN
//nTran
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'nTran');
IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading nTran', eSevr:=TcEventSeverity.Critical); END_IF
jsonnTran := fbJson.FindMember(jsonValue, 'nTran');
arrStates[Index].stBeamParams.nTran := TO_REAL(fbJson.GetString(jsonnTran));
//nRate
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'nRate');
IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading nRate', eSevr:=TcEventSeverity.Critical); END_IF
jsonnRate := fbJson.FindMember(jsonValue, 'nRate');
arrStates[Index].stBeamParams.nRate:= TO_UDINT(fbJson.GetString(jsonnRate));
//neVRange
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'neVRange');
jsonParam := fbJson.FindMember(jsonValue, 'neVRange');
sEV:=(fbJson.GetString(jsonParam));
bHasAllParameters R= NOT (LEN(sEV)=32);
This^.arrStates[Index].stBeamParams.neVRange :=0;
IF (NOT bHasAllParameters) THEN
fbLogger(sMsg:= 'Error Loading eVRange', eSevr:=TcEventSeverity.Critical);
ELSE
FOR nIndex := 0 TO PMPS_GVL.g_cBoundaries BY 1 DO
This^.arrStates[Index].stBeamParams.neVRange := SHL(This^.arrStates[Index].stBeamParams.neVRange,1);
This^.arrStates[Index].stBeamParams.neVRange := This^.arrStates[Index].stBeamParams.neVRange+HEXASCNIBBLE_TO_BYTE(sEV[nIndex]);
END_FOR
END_IF
//nBeamClassRange
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'nBeamClassRange');
jsonParam := fbJson.FindMember(jsonValue, 'nBeamClassRange');
sBC:=(fbJson.GetString(jsonParam));
bHasAllParameters R= NOT (LEN(sBC)=15);
arrStates[Index].stBeamParams.nBCRange :=0;
IF (NOT bHasAllParameters) THEN
fbLogger(sMsg:= 'Error Loading Beam Class Range. Make sure Bitmask is equal than 15 bits.', eSevr:=TcEventSeverity.Critical);
ELSE
FOR nIndex := 0 TO 14 BY 1 DO
arrStates[Index].stBeamParams.nBCRange := SHL(arrStates[Index].stBeamParams.nBCRange,1);
arrStates[Index].stBeamParams.nBCRange := arrStates[Index].stBeamParams.nBCRange+HEXASCNIBBLE_TO_BYTE(sBC[nIndex]);
END_FOR
END_IF
//ApertureName and Values
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'ap_name');
jsonParam := fbJson.FindMember(jsonValue, 'ap_name');
sAperture := fbJson.GetString(jsonParam);
{IF defined (L)}
IF ('SL1L0' = sAperture) THEN nAperture := L_Apertures.SL1L0;
ELSIF ('SL2L0' = sAperture) THEN nAperture := L_Apertures.SL2L0;
END_IF
{ELSIF defined (K)} // Attribute 'to_string' only build 4024 TO_STRING(PMPS.K_Apertures.SL1K0) - need another non hardcoded way
IF ('SL1K0' = sAperture) THEN nAperture := K_Apertures.SL1K0;
ELSIF ('SL2K0' = sAperture) THEN nAperture := K_Apertures.SL2K0;
END_IF
{END_IF}
IF (nAperture>-1) THEN
//Gap Height
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'ap_ygap');
jsonParam := fbJson.FindMember(jsonValue, 'ap_ygap');
arrStates[Index].stBeamParams.astApertures[nAperture].Height := TO_REAL(fbJson.GetString(jsonParam));
//Gap Width
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'ap_xgap');
jsonParam := fbJson.FindMember(jsonValue, 'ap_xgap');
arrStates[Index].stBeamParams.astApertures[nAperture].Width := TO_REAL(fbJson.GetString(jsonParam));
IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading aperture gap', eSevr:=TcEventSeverity.Critical); END_IF
END_IF
//Read Transition ID for the State
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'id');
jsonParam := fbJson.FindMember(jsonValue, 'id');
IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading Assertion ID', eSevr:=TcEventSeverity.Critical); END_IF
arrStates[Index].nRequestAssertionID := TO_UDINT(fbJson.GetInt(jsonParam));
//Set to True to indicate this state has been initialized
arrStates[Index].bBeamParamsLoaded := bHasAllParameters;
//Read additional Non-BP Parameters
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'reactive_temp');
jsonParam := fbJson.FindMember(jsonValue, 'reactive_temp');
IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading Temperature Threshold', eSevr:=TcEventSeverity.Critical); END_IF
arrStates[Index].stReactiveParams.nTempSP := TO_REAL(fbJson.GetString(jsonParam));
bHasAllParameters R= NOT fbJson.HasMember(jsonValue, 'reactive_pressure');
jsonParam := fbJson.FindMember(jsonValue, 'reactive_pressure');
IF (NOT bHasAllParameters) THEN fbLogger(sMsg:= 'Error Loading Pressure Threshold', eSevr:=TcEventSeverity.Critical); END_IF
arrStates[Index].stReactiveParams.nPressSP := TO_REAL(fbJson.GetString(jsonParam));
END_IF
IF NOT( bHasAllParameters) THEN
nErrId := 900+Step;
bError := TRUE;
sErrMsg:= CONCAT('Error loading BP Data - Device-State: ',sStateName);
fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical);
END_IF
M_LoadSafeBP:=bHasAllParameters;
//TODO
//ADD the rest of the BP
END_METHOD
FB_JsonFileToJsonDoc
FUNCTION_BLOCK FB_JsonFileToJsonDoc
VAR_INPUT
bExecute : BOOL; //Rising Edge
sPLCName : STRING;
sSrcNetId : T_AmsNetId;
sSrcPathName : T_MaxString;
END_VAR
VAR_OUTPUT
PMPS_jsonDoc : SJsonValue;
bHasPLC : BOOL;
bBusy : BOOL;
bError : BOOL;
nErrId : UDINT;
sErrMsg :STRING;
END_VAR
VAR_IN_OUT
io_fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR
(*Get AMS Net ID*)
fb_GetLocalAmsNetId: FB_GetLocalAmsNetId;
(* JSON *)
fbJson : FB_JsonDomParser;
jsonDoc : SJsonValue;
jsonProp : SJsonValue;
(*File*)
fbFileOpen : FB_FileOpen;
fbFileClose : FB_FileClose;
fbFileRead : FB_FileRead;
hSrcFile : UINT := 0;(* File handle of the source file *)
Step: INT;
index: DINT;
RisingEdge : R_TRIG;
(* Buffer *)
sbuffRead :STRING(100000);
cbReadLength : UDINT := 0;
nFileLength : UDINT := 0;
bfbJsonExceptionRaised: BOOL;
tTimeOut : TIME := DEFAULT_ADS_TIMEOUT;
bInit : BOOL;
(*Logger*)
tNewMessage : R_TRIG;
fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.MPS, nMinTimeViolationAcceptable:=10);
(*FFO*)
FFO : FB_FastFault :=(
i_Desc := 'Fault occurs when there is an error reading json file',
i_TypeCode := 16#FF13);
END_VAR
(* Get Local AMS net ID*)
IF NOT (bInit) THEN
fb_GetLocalAmsNetId.bExecute := TRUE;
bInit := TRUE;
END_IF
fb_GetLocalAmsNetId(bExecute:=);
(* Retrieve JSON content *)
RisingEdge(CLK:=bExecute);
CASE Step OF
0: (* Idle state *)
IF RisingEdge.Q THEN
bBusy := TRUE;
bError:= FALSE;
nErrId:=0;
Step := 1;
cbReadLength:=0;
hSrcFile:=0;
fbLogger(sMsg:='Start Reading Json File.', eSevr:=TcEventSeverity.Info);
//Clear all json values and read buffer;
PMPS_jsonDoc := 0;
jsonDoc:=0;
jsonProp:=0;
FOR index:=1 TO 100000 DO
sbuffRead[index]:=0; //clear buffer
END_FOR
sErrMsg:='';
nFileLength:=0;
jsonDoc := fbJson.ParseDocument(sbuffRead);
END_IF
1: (* Open source file *)
sSrcNetId := fb_GetLocalAmsNetId.AddrString;
fbFileOpen( bExecute := FALSE );
fbFileOpen( sNetId := sSrcNetId, sPathName := sSrcPathName,
nMode := FOPEN_MODEREAD OR FOPEN_MODETEXT,
ePath := PATH_GENERIC, tTimeout := tTimeOut, bExecute := TRUE );
Step := Step + 1;
2: (* Check Open file was successful*)
fbFileOpen( bExecute := FALSE );
IF NOT fbFileOpen.bBusy THEN
IF fbFileOpen.bError THEN
nErrId := fbFileOpen.nErrId;
bError := TRUE;
sErrMsg:= CONCAT('Error Opening Json File. E:', TO_STRING(nErrId));
Step := 900 + Step;
ELSE
hSrcFile := fbFileOpen.hFile;
Step := Step + 1;
END_IF
END_IF
3: (* Read data from source file to buffer string*)
cbReadLength := 0;
fbFileRead( bExecute:= FALSE );
fbFileRead( sNetId:=sSrcNetId, hFile:=hSrcFile,
pReadBuff:= ADR(sbuffRead), cbReadLen:= SIZEOF(sbuffRead),
bExecute:=TRUE, tTimeout:=tTimeOut );
Step := Step + 1;
4: (* Check read file was successful and data read in buffer string*)
fbFileRead( bExecute:= FALSE );
IF NOT fbFileRead.bBusy THEN
IF fbFileRead.bError THEN
nErrId := fbFileRead.nErrId;
bError := TRUE;
sErrMsg:= CONCAT('Error reading Json File. E: ', TO_STRING(nErrId));
Step := 900 +Step;
ELSE
cbReadLength := fbFileRead.cbRead;
nFileLength := nFileLength + cbReadLength;
Step := Step + 1 ;
END_IF
END_IF
5: (* Check Bytes read*)
IF( nFileLength =0) THEN // oh no, we read zero bytes
nErrId := fbFileRead.nErrId;
bError := TRUE;
sErrMsg:= CONCAT( 'Zero bytes were read from the Json File. E:', TO_STRING(nErrId));
Step := 900 +Step;
ELSE // we read something, lets log about it
fbLogger(sMsg:=CONCAT('Total numbers of Bytes read from file : ', TO_STRING(nFileLength)), eSevr:=TcEventSeverity.Info);
Step := Step + 1 ;
END_IF;
6: (* Close source file *)
fbFileClose( bExecute := FALSE );
fbFileClose( sNetId:=sSrcNetId, hFile:=hSrcFile, bExecute:=TRUE, tTimeout:=tTimeOut );
Step := Step + 1;
7 : (*Check file was closed successfully.*)
fbFileClose( bExecute := FALSE );
IF NOT fbFileClose.bBusy THEN
IF fbFileClose.bError THEN
nErrId := fbFileClose.nErrId;
bError := TRUE;
Step:=900+Step;
sErrMsg:= CONCAT('Error closing Json File. E:', TO_STRING(nErrId));
END_IF
Step := Step + 1;
hSrcFile := 0;
END_IF
8: (* Error or ready => Cleanup *)
IF (hSrcFile <> 0 ) THEN
Step := 6; (* Close the source file *)
ELSE
(* Ready *)
Step := 10;
END_IF
10: (*Parse read buffer string to JSON DOM object*)
//cbJsonParserLength := fbJson.CopyDocument(sbuffRead,cbReadLength);
jsonDoc := fbJson.ParseDocument(sbuffRead);
bfbJsonExceptionRaised:= fbJson.ExceptionRaised();
IF (jsonDoc <> 0) AND NOT (bfbJsonExceptionRaised) THEN
Step := 20;
ELSE
Step := 900 + Step;
nErrId := Step;
bError := TRUE;
sErrMsg:= CONCAT('Error Parsing JSON Data. S:',TO_STRING(nErrId));
END_IF
20: (*Find the PLC Name*)
bHasPLC := fbJson.HasMember(jsonDoc, sPLCName);
IF (bHasPLC) THEN
jsonProp := fbJson.FindMember(jsonDoc, sPLCName);
IF jsonProp <> 0 THEN
Step := Step+1;
ELSE
Step := 900 + Step;
nErrId := Step;
bError := TRUE;
sErrMsg:= CONCAT('Error Loading PLC data from Json File : ',sPLCName);
END_IF
ELSE
Step := 900 + Step;
nErrId := Step;
bError := TRUE;
sErrMsg:= CONCAT('Error PLC name not found in Json File : ',sPLCName);
END_IF
21: (*write to the PMPS JSonDoc variable*)
PMPS_jsonDoc := jsonProp;
Step := 30;
30: (*ALL good here*)
fbLogger(sMsg:='Reading and Parsing Json File Completed Successfully.', eSevr:=TcEventSeverity.Info);
// Release allocated memory
//jsonDoc := fbJson.NewDocument();
bExecute:= FALSE;
bBusy:=FALSE;
FFO.i_xOK := TRUE;
Step := 0;
END_CASE
//Fault in error state
IF(Step>=900) THEN
FFO.i_xOK := FALSE;
bBusy:=FALSE;
Step:=0;
END_IF
ACT_FFO();
ACT_LOGGER();
END_FUNCTION_BLOCK
ACTION ACT_FFO:
(*FAST FAULT*)
FFO(i_xOK := ,
i_xReset := ,
i_xAutoReset :=TRUE,
i_DevName := sPLCName,
io_fbFFHWO := io_fbFFHWO);
END_ACTION
ACTION ACT_Logger:
// Log valve open
tNewMessage(CLK:= NOT(FbLogger.sMsg = sErrMsg) AND NOT (sErrMsg =''));
IF tNewMessage.Q THEN fbLogger(sMsg:= sErrMsg, eSevr:=TcEventSeverity.Critical); END_IF
END_ACTION
- Related:
FB_KPhotonEnergy
FUNCTION_BLOCK FB_KPhotonEnergy EXTENDS FB_PhotonEnergy
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'pytmc' := '
pv: eEnrg
link: BEND:DMPS:400:BACT
field: EGU GeV
'}
fbSxuElectronEnergy : FB_LREALFromEPICS;
{attribute 'pytmc' := '
pv: EEnergy
io: i
field: DESC Electron Energy
field: EGU GeV
'}
fElectronEnergyVal : LREAL;
{attribute 'pytmc' := '
pv: EEnergyValid
io: i
field: DESC Electron Energy Valid
'}
bElectronEnergyValid : BOOL;
{attribute 'pytmc' := '
pv: UND
link: USEG:UNDS:
'}
fbSxu : FB_SXU;
END_VAR
// Update PVs
fbSxuElectronEnergy();
fElectronEnergyVal := fbSxuElectronEnergy.fValue;
bElectronEnergyValid := fbSxuElectronEnergy.bValid;
fbSxu(fbElectronEnergy:=fbSxuElectronEnergy);
SUPER^(BP:=SUPER^.BP, Undulator:=fbSxu );
END_FUNCTION_BLOCK
- Related:
FB_KStopper
FUNCTION_BLOCK FB_KStopper EXTENDS FB_StopperWatcher
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
END_VAR
VAR
END_VAR
SUPER^(stCurrentBP:=SUPER^.stCurrentBP);
END_FUNCTION_BLOCK
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
eStopper : K_Stopper;
sStopperName : STRING;
END_VAR
Stopper := eStopper;
StopperName := sStopperName;
END_METHOD
- Related:
FB_KVetoDevice
FUNCTION_BLOCK FB_KVetoDevice EXTENDS FB_VetoDevice
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
SUPER^(stCurrentBP:=SUPER^.stCurrentBP);
END_FUNCTION_BLOCK
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
eVetoDeviceIN : K_Stopper := K_Stopper.DEFAULT;
eVetoDeviceOUT : K_Stopper := K_Stopper.DEFAULT;
sVetoDeviceName : STRING;
END_VAR
VetoDevice_IN := eVetoDeviceIN;
VetoDevice_OUT := eVetoDeviceOUT;
VetoDeviceName := sVetoDeviceName;
END_METHOD
- Related:
FB_LinearDeviceStateTable
(* This function block implements simple database. Data element values are stored in the hash table. *)
{attribute 'no_check'}
FUNCTION_BLOCK FB_LinearDeviceStateTable
VAR_INPUT
key : DWORD := 0;(* Entry key: used by A_Lookup, A_Remove method, the key variable is also used by A_Add method *)
putPosPtr : POINTER TO T_HashTableEntry := 0;(* Hash table entry position pointer (used by A_Find, A_GetNext, A_GetPrev) *)
putValue : ST_DeviceState;(* Hash table entry value (used by A_AddHead, A_AddTail, A_Find )*)
END_VAR
VAR_OUTPUT
bOk : BOOL := FALSE;(* TRUE = Success, FALSE = Failed *)
getPosPtr : POINTER TO T_HashTableEntry := 0;(* Returned hash table entry position pointer *)
getValue : ST_DeviceState;(* Returned hash table entry value *)
nCount : UDINT := 0;(* Hash table size (number of used entries, used by A_Count) *)
END_VAR
VAR
dataPool : ARRAY[0..PMPS_GVL.MAX_DEVICE_STATES] OF ST_DeviceState;(* Structured data element pool *)
entries : ARRAY[0..PMPS_GVL.MAX_DEVICE_STATES] OF T_HashTableEntry;(* Max. number of hash table entries. The value of table entry = 32 bit integer (pointer to dataPool-array-entry) *)
fbTable : FB_HashTableCtrl;(* basic hash table control function block *)
hTable : T_HHASHTABLE;(* hash table handle *)
pRefPtr : POINTER TO ST_DeviceState := 0;
indexOfElem : ULINT;(* Integer value (max. size: x86=>32bit, x64=>64bit)*)
END_VAR
;
END_FUNCTION_BLOCK
ACTION A_Add:
(* Adds entry to the table *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
fbTable.A_Add( hTable := hTable, key := key, putValue := 16#0000_0000(* we will set this value later *), getPosPtr=>getPosPtr, bOk=>bOk );(* Add new element to the table, getPosPtr points to the new entry *)
IF fbTable.bOk THEN(* Success *)
fbTable.A_GetIndexAtPosPtr( hTable := hTable, putPosPtr := getPosPtr, getValue =>indexOfElem, bOk=>bOk );(* Get array index of getPosPtr entry *)
IF fbTable.bOk THEN(* Success *)
pRefPtr := ADR( dataPool[indexOfElem] );(* Get pointer to the data element *)
pRefPtr^ := putValue;(* copy application value *)
fbTable.A_Add( hTable := hTable, key := key, putValue := pRefPtr, bOk=>bOk );(* Assign the entry value = pointer to the data element *)
IF fbTable.bOk THEN(* Success *)
getValue := putValue;
END_IF
END_IF
END_IF
END_ACTION
ACTION A_Count:
(* Count number of used entries *)
nCount := hTable.nCount;
bOk := TRUE;
END_ACTION
ACTION A_GetFirst:
(* Get first entry position pointer and value *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
fbTable.A_GetFirst( hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := pRefPtr^;
END_IF
END_ACTION
ACTION A_GetNext:
(* Get next entry position pointer and value *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
bOk := FALSE;
IF putPosPtr = 0 THEN
RETURN;
END_IF
fbTable.A_GetNext( hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := pRefPtr^;
END_IF
END_ACTION
ACTION A_Lookup:
(* Lookup for entry by key *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
fbTable.A_Lookup( key := key, hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := pRefPtr^;
END_IF
END_ACTION
ACTION A_Remove:
(* Search for entry and remove it *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
fbTable.A_Remove( key := key, hTable := hTable, putPosPtr := putPosPtr, getValue=>pRefPtr, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := pRefPtr^;
END_IF
END_ACTION
ACTION A_Reset:
(* Reset/initialize linked list *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;
bOk := F_CreateHashTableHnd( ADR( entries ), SIZEOF( entries ), hTable );(* Intialize table handle *)
fbTable.A_Reset( hTable := hTable, bOk=>bOk );
nCount := 0;
END_ACTION
- Related:
FB_LinearGovernor
(* The governor is meant to check your work and keep you from making mistakes.
Checks:
Does your state match the device position and visa versa?
Are the beam parameters safe for the device state?
Did you properly assert the transition and final beam parameters?
*)
FUNCTION_BLOCK FB_LinearGovernor
VAR_INPUT
i_stCurrentBeamParams : ST_BeamParams; //Link to global beam params
i_xMPSOverride : BOOL; //True releases MPS limits after a delay
i_xTransitionRequested : BOOL; //Set true to request a transition
i_xResetMPSFault : BOOL; //Set true to clear MPS fault
END_VAR
VAR_OUTPUT
//True when governor has released restrictions
q_xTransitionPermitted : BOOL;
//Set if MC_Power has an error, or override mode is requested
q_xFault : BOOL;
// Indicates override mode is active
q_xMPSLimitsOverridden: BOOL;
END_VAR
VAR_IN_OUT
stDevice : ST_Device; //The governed
Arbiter : FB_Arbiter;
FastFaultOutput : FB_HardwareFFOutput;
END_VAR
VAR
mcPower : MC_Power;
xActuatorPositiveEnable: BOOL;
xActuatorNegativeEnable: BOOL;
xMPSPositiveEnable : BOOL;
xMPSNegativeEnable : BOOL;
lrPosition: LREAL;
lrLatchedTargetPosition : LREAL;
lrLatchedTargetTolerance : LREAL;
stLatchedTargetState : ST_DeviceStateExt;
lrMPSUpperLimit: LREAL;
lrMPSLowerLimit: LREAL;
rtNewState: R_TRIG;
xTransitionOK: BOOL;
tonMPSOverrideMode : TON := (
PT := T#5S
);
srMPSFault : SR;
// Actuator out of bounds fault
xActuatorOOB: BOOL;
//Beam parameters out of spec
xBeamParamsOOS: BOOL;
//Beam parameters of currently latched state
stLatchedBeamParams: ST_BeamParams;
stTargetState: ST_DeviceStateExt;
rtLatchNewState : R_TRIG;
xInit: BOOL := TRUE;
stInitDeviceState: ST_DeviceStateExt;
// Indicates an error with initialization
xInitFault: BOOL;
// Indicates the state could not be found in the state table. There may be something wrong in the device state machine implementation or state table instantiation.
xStateLookupError: BOOL;
// A transition needs to be requested at least once before we start latching any state.
xTransitionHasBeenRequested: BOOL := FALSE;
ffBeamParamsOK : FB_FastFault;
ffActuatorBoundsOK : FB_FastFault;
END_VAR
lrPosition := stDevice.stAxis.NcToPlc.ActPos; //Actuator position
stDevice.stAxis.ReadStatus();
//Init
(* The init strategy is:
If the device is restored and it's residing at the safe position,
no PMPS faults. *)
////////////////////////////////
IF xInit THEN
xInit := FALSE;
//Use nSafeSate for initialization.
stDevice.StateTable.A_Lookup(key := stDevice.nSafeState);
IF stDevice.StateTable.bOk THEN
stInitDeviceState := F_DeviceState_To_DeviceStateExt(stDevice.StateTable.getValue, TRUE);
xInitFault := FALSE;
ELSE
xInitFault := TRUE;
stInitDeviceState.xValid := FALSE;
END_IF
lrLatchedTargetPosition := stInitDeviceState.rPosition;
lrLatchedTargetTolerance := stInitDeviceState.rTolerance;
stLatchedBeamParams := stInitDeviceState.stReqBeamParam;
END_IF
// The governor will permit transitions once it verifies the transition assertion is active
// Note the governor does not verify the target state beam params are asserted, perhaps it should <TODO>
////////////////////////////////
IF i_xTransitionRequested THEN
xTransitionHasBeenRequested := TRUE;
xTransitionOK := Arbiter.CheckRequest( stDevice.stTransitionState.nStateRef) AND F_SafeBPCompare(i_stCurrentBeamParams, stDevice.stTransitionState.stReqBeamParam);
//Set the target state for evaluation in further logic
stdevice.StateTable.A_Lookup(key := stDevice.nTargetState);
IF stDevice.StateTable.bOk THEN
stTargetState := F_DeviceState_To_DeviceStateExt(stDevice.StateTable.getValue, TRUE);
xStateLookupError := FALSE;
ELSE
xStateLookupError := TRUE;
stTargetState.xValid := FALSE;
END_IF
ELSE
xTransitionOK := FALSE;
END_IF
(* Note about TransitionRequested
If TransitionRequested goes false during a transition, an MPS fault may be induced. This occurs
because the Actuator Out of Bounds fault will go back to using the original state limits, which
the axis may have already moved beyond.
To avoid this transient fault, make sure your device state machine is implemented such that the
transition request to the governor block remains high until the target position is reached.
*)
//Determine if a transition is complete and latch the new state
(* A transition is complete when:
Axis at standstill.
Axis within tolerance.
No transition req.
Once these conditions are met, the MPS limits shall re-engage to restrict the
axis motion to the tolerance around the state position and the MPS fault monitors
shall evaluate based on the newly latched target state.
*)
rtLatchNewState(CLK := stDevice.stAxis.Status.StandStill AND
lrPosition <= stTargetState.rPosition + stTargetState.rTolerance AND
lrPosition >= stTargetState.rPosition - stTargetState.rTolerance AND
NOT i_xTransitionRequested AND xTransitionHasBeenRequested);
IF rtLatchNewState.Q THEN
lrLatchedTargetPosition := stTargetState.rPosition;
lrLatchedTargetTolerance := stTargetState.rTolerance;
stLatchedBeamParams := stTargetState.stReqBeamParam;
stLatchedTargetState := stTargetState;
END_IF
//Let other blocks know the governor won't interfer with transitions
q_xTransitionPermitted := xTransitionOK;
//Adjust limit ranges based on active state and current target
IF xTransitionOK THEN
//Extend range to include target state
lrMPSUpperLimit := MAX(lrMPSUpperLimit, stTargetState.rPosition + stTargetState.rTolerance);
lrMPSLowerLimit := MIN(lrMPSLowerLimit, stTargetState.rPosition - stTargetState.rTolerance);
ELSE
//Calculate the limit positions for MPS based on current state
lrMPSUpperLimit := lrLatchedTargetPosition + lrLatchedTargetTolerance;
lrMPSLowerLimit := lrLatchedTargetPosition - lrLatchedTargetTolerance;
END_IF
//MPS override
(* In override mode, the device will be free to move anywhere within it's travel range *)
//Permit motion after a small delay
tonMPSOverrideMode(IN:=i_xMPSOverride OR stDevice.xOverrideMPSLimits, Q=>q_xMPSLimitsOverridden);
//Virtual limit switch evaluation
xActuatorPositiveEnable := stDevice.i_xHiLim AND lrPosition <= stDevice.lrUpperPositionLimit;
xActuatorNegativeEnable := stDevice.i_xLoLim AND lrPosition >= stDevice.lrLowerPositionLimit;
//Evaluate MPS limit states
xMPSPositiveEnable := lrPosition <= lrMPSUpperLimit;
xMPSNegativeEnable := lrPosition >= lrMPSLowerLimit;
//Final Limit switch evaluation
mcPower.Enable_Positive := xActuatorPositiveEnable AND (tonMPSOverrideMode.Q OR xMPSPositiveEnable);
mcPower.Enable_Negative := xActuatorNegativeEnable AND (tonMPSOverrideMode.Q OR xMPSNegativeEnable);
mcPower(
Axis := stDevice.stAxis,
Enable := stDevice.xEnable //link to device enable <TODO>
);
//MPS Faults
//Fault if the beam is unsafe for current state
ffBeamParamsOK(i_xOK:=F_SafeBPCompare(i_stCurrentBeamParams, stLatchedBeamParams),
i_xReset:=i_xResetMPSFault,
io_fbFFHWO := FastFaultOutput);
//Fault if the position is outside of the permitted range
ffActuatorBoundsOK(i_xOK := (lrPosition <= lrMPSUpperLimit AND lrPosition >= lrMPSLowerLimit),
i_xReset:=i_xResetMPSFault,
io_fbFFHWO := FastFaultOutput);
//Collect all other faults
q_xFault := NOT ffBeamParamsOK.o_xFFLine OR
NOT ffActuatorBoundsOK.o_xFFLine OR
mcPower.Error OR
i_xMPSOverride OR
xInitFault;
//Any faults trip the fast fault line for this FB
//FastFaultOutput.CheckIn(NOT q_xFault);
END_FUNCTION_BLOCK
FB_LPhotonEnergy
FUNCTION_BLOCK FB_LPhotonEnergy EXTENDS FB_PhotonEnergy
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'pytmc' := '
pv: eEnrg
link: BEND:DMPH:400:BACT
field: EGU GeV
'}
fbHgvpuElectronEnergy : FB_LREALFromEPICS;
{attribute 'pytmc' := '
pv: EEnergy
io: i
field: DESC Electron Energy
field: EGU GeV
'}
fElectronEnergyVal : LREAL;
{attribute 'pytmc' := '
pv: EEnergyValid
io: i
field: DESC Electron Energy Valid
'}
bElectronEnergyValid : BOOL;
{attribute 'pytmc' := '
pv: UND
link: USEG:UNDH:
'}
fbHgvpu : FB_Hgvpu;
END_VAR
// Update PVs
fbHgvpuElectronEnergy();
fElectronEnergyVal := fbHgvpuElectronEnergy.fValue;
bElectronEnergyValid := fbHgvpuElectronEnergy.bValid;
fbHgvpu(fbElectronEnergy:=fbHgvpuElectronEnergy);
SUPER^(BP:=SUPER^.BP, Undulator:=fbHgvpu );
END_FUNCTION_BLOCK
- Related:
FB_LStopper
FUNCTION_BLOCK FB_LStopper EXTENDS FB_StopperWatcher
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
END_VAR
VAR
END_VAR
SUPER^(stCurrentBP:=SUPER^.stCurrentBP);
END_FUNCTION_BLOCK
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
eStopper : L_Stopper;
sStopperName : STRING;
END_VAR
Stopper := eStopper;
StopperName := sStopperName;
END_METHOD
- Related:
FB_LVetoDevice
FUNCTION_BLOCK FB_LVetoDevice EXTENDS FB_VetoDevice
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
SUPER^(stCurrentBP:=SUPER^.stCurrentBP);
END_FUNCTION_BLOCK
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
eVetoDeviceIN : L_Stopper := L_Stopper.DEFAULT;
eVetoDeviceOUT : L_Stopper := L_Stopper.DEFAULT;
sVetoDeviceName : STRING;
END_VAR
VetoDevice_IN := eVetoDeviceIN;
VetoDevice_OUT := eVetoDeviceOUT;
VetoDeviceName := sVetoDeviceName;
END_METHOD
- Related:
FB_MachineModeFromEPICS
FUNCTION_BLOCK FB_MachineModeFromEPICS
(*
Readback options are:
0 – NC (normal conducting, copper linac)
1 – SC (superconducting linac)
2 – Misconfigured (something in link node not set correctly.
*)
VAR_IN_OUT
BP : ST_BeamParams;
fbMPS_MachineMode : FB_LREALFromEPICS;
FFO : FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
xError : BOOL;
END_VAR
VAR
ffModeReadBack : FB_FastFault := (
i_DevName := 'Arbiter',
i_Desc := 'Issue with Machine mode readback from Accelerator. Gateway or EPICS connection. Must be fixed.',
i_TypeCode := 16#313,
i_xAutoReset:=True);
END_VAR
VAR CONSTANT
cFailSafeMM : USINT := 3;
END_VAR
fbMPS_MachineMode();
IF fbMPS_MachineMode.bValid THEN
BP.nMachineMode := LREAL_TO_USINT(fbMPS_MachineMode.fValue);
ELSE
BP.nMachineMode := cFailSafeMM;
END_IF
ffModeReadBack(i_xOK:=(fbMPS_MachineMode.bValid) AND fbMPS_MachineMode.fValue <2 , io_fbFFHWO:=FFO);
BP.xValid R= NOT fbMPS_MachineMode.bValid;
END_FUNCTION_BLOCK
- Related:
FB_MachineSimulator
(* Simulates the machine responding to requests by adding a bit of delay/ ramps
*)
FUNCTION_BLOCK FB_MachineSimulator
VAR_INPUT
i_stAssertedParams : ST_BeamParams;
i_xFault : BOOL;
xEnableAtt : BOOL;
xEnablePE : BOOL;
xEnableRate : BOOL;
END_VAR
VAR_IN_OUT
iq_stMachineParams : ST_BeamParams;
END_VAR
VAR
nTargetTran: REAL;
tonTranTimer : TON := (
PT := T#2S
);
fTargetPP_mJ: REAL;
tonPPETimer: TON := (
PT := T#2S
);
END_VAR
//Attenuation
IF xEnableAtt THEN
IF i_stAssertedParams.nTran <> nTargetTran THEN
tonTranTimer(IN:=FALSE);
nTargetTran := i_stAssertedParams.nTran;
ELSIF NOT tonTranTimer.Q THEN
tonTranTimer(IN:=TRUE);
ELSE
iq_stMachineParams.nTran := nTargetTran;
END_IF
END_IF
//Pulse Energy
(*
IF xEnablePE THEN
IF i_stAssertedParams.fPP_mJ <> fTargetPP_mJ THEN
tonPPETimer(IN:=FALSE);
fTargetPP_mJ := i_stAssertedParams.fPP_mJ;
ELSIF NOT tonTranTimer.Q THEN
tonPPETimer(IN:=TRUE);
ELSE
iq_stMachineParams.fPP_mJ := fTargetPP_mJ;
END_IF
END_IF
*)
//Rate
IF xEnableRate THEN
IF i_xFault THEN
iq_stMachineParams.nRate := 0;
ELSE
iq_stMachineParams.nRate := i_stAssertedParams.nRate;
END_IF
END_IF
END_FUNCTION_BLOCK
- Related:
FB_PhotonEnergy
FUNCTION_BLOCK FB_PhotonEnergy
VAR_INPUT
Undulator : I_UndulatorComplex;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
BP : ST_BeamParams;
END_VAR
VAR
nCurrentPhotonEnergyBitmask : UDINT;
nTargetPhotonEnergyBitmask : UDINT;
END_VAR
//Update BP
nCurrentPhotonEnergyBitmask := F_eVRangeCalculator(Undulator.rCurrentPhotonEnergy, nCurrentPhotonEnergyBitmask);
nTargetPhotonEnergyBitmask := F_eVRangeCalculator(Undulator.rTargetPhotonEnergy, nTargetPhotonEnergyBitmask);
BP.neVRange := nCurrentPhotonEnergyBitmask OR nTargetPhotonEnergyBitmask;
END_FUNCTION_BLOCK
- Related:
FB_PhotonEnergyWatcher
(*
A. Wallace 2019-4-22
The photon energy watcher ensures the current and target photon energy is within
the arbirated bounds. Target in this case means, the calculated target photon energy
from the PVs that control the mechatronics of the undulators/ the electron energy.
If there are control PVs that match the monitor PVs, the control PVs are used to
calculate a "target" photon energy. This is supposed to cover when the undulators/
electron energy is transitioning.
The abritrated bounds come from a simple AND of all the permitted ranges. See
the arbitrate action of the arbiter FB.
Note, this protection logic does not account for beam-off when determining fast-fault
status. If a device is requesting a limited range of eV, this request must be honored,
regardless of current beam-rate.
*)
{attribute 'reflection'}
FUNCTION_BLOCK FB_PhotonEnergyWatcher
VAR_INPUT
i_stCurrentBeamParams : ST_BeamParams; //Link to global beam params
i_stMachineTargetBeamParams : ST_BeamParams; //Link to global machine target beam params
i_stRequestedBeamParams : ST_BeamParams; //Link to arbiter output or beam param. requestor
// Reset fault
i_xReset: BOOL;
sName : STRING := 'PhotonEnergyWatcher';
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
io_fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR
xPhotonEnergyWithinBounds : BOOL;
fbFF : FB_FastFault :=(
i_DevName := sName,
i_Desc := 'Fault occurs when the calculated machine photon energy (K value calculated by undulator gap, and electron energy) falls outside the permitted range.',
i_TypeCode := 7 );
{attribute 'pytmc' := '
pv: ResidualPhotonEnergies
io: i
archive: 1Hz monitor
field: DESC Portions of beam eV not permitted
field: EGU eV-bitmask
'}
evResidual : DWORD;
fbLog : FB_LogMessage := (
eSubSystem := E_Subsystem.MPS,
eSevr := TcEventSeverity.Critical
);
bLogOneShot : BOOL;
sDevName : T_MaxString := 'Photon Energy Watcher';
fbGetHN : FB_GetHostName;
bInit : BOOL := TRUE;
{attribute 'instance-path'}
{attribute 'noinit'}
sPath : T_MaxString;
fbStr : FB_FormatString := (
sOut := 'Non-zero photon energy residual: %32b; Req: %32b; Act: %32b');
END_VAR
IF bInit THEN
fbGetHN(bExecute:=TRUE);
bInit R= NOT fbGetHN.bBusy;
END_IF
xPhotonEnergyWithinBounds := (i_stCurrentBeamParams.neVRange AND i_stRequestedBeamParams.neVRange) = i_stCurrentBeamParams.neVRange;
evResidual := (i_stCurrentBeamParams.neVRange XOR i_stRequestedBeamParams.neVRange) AND i_stCurrentBeamParams.neVRange;
IF evResidual <> 0 AND bLogOneShot THEN
fbLog.sJson := F_PMPS_JSON(
CONCAT(fbGetHN.sHostName, sDevName),
sPath,
PMPS_CODES.PEW_FAULT);
fbStr.arg1 := F_DWORD(evResidual);
fbStr.arg2 := F_DWORD(i_stCurrentBeamParams.neVRange);
fbStr.arg3 := F_DWORD(i_stRequestedBeamParams.neVRange);
fbStr();
fbLog(sMsg:=fbStr.sOut);
bLogOneShot := FALSE;
ELSIF evResidual = 0 THEN
bLogOneShot := TRUE;
END_IF
fbFF(i_xOK := xPhotonEnergyWithinBounds,
io_fbFFHWO := io_fbFFHWO,);
END_FUNCTION_BLOCK
FB_PhotonEnergyWatcher_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_PhotonEnergyWatcher_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
PEWatcherBasicFunction();
END_FUNCTION_BLOCK
METHOD PEWatcherBasicFunction
VAR_INPUT
END_VAR
VAR_INST
fbPEW : FB_PhotonEnergyWatcher;
fbFFO : FB_HardwareFFOutput;
q_Output : BOOL;
stCurBeamParams : ST_BeamParams;
stMachineTargetBeamParams : ST_BeamParams;
stReqBeamParams : ST_BeamParams;
END_VAR
TEST('PEWatcherBasicFunction');
//Clear FFO fault to start
fbFFO(i_xReset:=TRUE);
//All eV OK
stCurBeamParams.neVRange := F_eVRangeCalculator(800, 0);
stMachineTargetBeamParams.neVRange := 2#0000_0000_1000_0000;
stReqBeamParams.neVRange := 2#1111_1111_1111_1111;
fbFFO(i_xReset:=FALSE); //Reset request released
fbPEW(i_stCurrentBeamParams := stCurBeamParams,
i_stMachineTargetBeamParams := stMachineTargetBeamParams,
i_stRequestedBeamParams := stReqBeamParams,
io_fbFFHWO := fbFFO,
i_xReset := TRUE); //Clear local FF within PEW
fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output); //This simulates a completed PLC cycle
//Evaluate output should be called once at the end of a PLC cycle.
AssertTrue(q_Output,
'Beam within limits, FFO should be true');
//Req eV moved out of range
stCurBeamParams.neVRange := F_eVRangeCalculator(800, 0);
stMachineTargetBeamParams.neVRange := F_eVRangeCalculator(800, 0);
stReqBeamParams.neVRange := 2#0000_1111_0011_1111;
fbPEW.i_xReset := FALSE; //release local FF reset req
fbPEW(i_stCurrentBeamParams := stCurBeamParams,
i_stMachineTargetBeamParams := stMachineTargetBeamParams,
i_stRequestedBeamParams := stReqBeamParams,
io_fbFFHWO := fbFFO);
fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output);
AssertFalse(q_Output,
'Limits moved beyond current and target beam, should produce a fault.');
//Beam and target eV moved back within permitted ranges, reset clears fault
stCurBeamParams.neVRange := F_eVRangeCalculator( 300, 0);
stMachineTargetBeamParams.neVRange := F_eVRangeCalculator(300, 0);
stReqBeamParams.neVRange := 2#0000_1111_0011_1111;
fbFFO(i_xReset:=TRUE); //New reset request
fbPEW.i_xReset := TRUE; //New reset request
fbPEW(i_stCurrentBeamParams := stCurBeamParams, //Beam params are now within spec
i_stMachineTargetBeamParams := stMachineTargetBeamParams,
i_stRequestedBeamParams := stReqBeamParams,
io_fbFFHWO := fbFFO);
fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output);
AssertTrue(q_Output,
'Beam within limits after reset, FFO should be true');
//At this point, the fast fault would be cleared
//Current beam eV moved out of range
stCurBeamParams.neVRange := F_eVRangeCalculator(800, 0); //Moved out of range
stMachineTargetBeamParams.neVRange := F_eVRangeCalculator(300, 0); //Still within range
stReqBeamParams.neVRange := 2#0000_1111_0011_1111;
fbPEW(i_stCurrentBeamParams := stCurBeamParams,
i_stMachineTargetBeamParams := stMachineTargetBeamParams,
i_stRequestedBeamParams := stReqBeamParams,
io_fbFFHWO := fbFFO);
fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output);
AssertFalse(q_Output,
'Current beam moved beyond set limits, should produce a fault');
//Target beam eV moved out of range
fbFFO(i_xReset:=FALSE); //Reset released
fbFFO(i_xReset:=TRUE); //New reset request comes in at the top of a cycle, but will be overridden
stCurBeamParams.neVRange := F_eVRangeCalculator(300, 0); //Now within range
stMachineTargetBeamParams.neVRange := F_eVRangeCalculator(800, 0); //Moved out of range
stReqBeamParams.neVRange := 2#0000_1111_0011_1111;
fbPEW.i_xReset := TRUE; //Attempt to reset local FF will fail
fbPEW(i_stCurrentBeamParams := stCurBeamParams,
i_stMachineTargetBeamParams := stMachineTargetBeamParams,
i_stRequestedBeamParams := stReqBeamParams,
io_fbFFHWO := fbFFO);
fbFFO.EvaluateOutput(q_xFastFaultOut=>q_Output);
AssertFalse(q_Output,
'Target beam moved beyond set limits, should produce a fault');
TEST_FINISHED();
END_METHOD
FB_PressSensor_FFO
FUNCTION_BLOCK FB_PressSensor_FFO
VAR_INPUT
{attribute 'pytmc' := '
pv: Press
io: input
field: EGU Torr
field: PREC 2
'}
rPress : REAL;
{attribute 'pytmc' := '
pv: FAULT_SP
io: input
field: EGU Torr
field: PREC 2
'}
fFaultThreshold : LREAL; //Faults when the threshold is reached. Trigger value.
{attribute 'pytmc' := '
pv: FAULT_SP_HYS
io: input
field: EGU %
field: PREC 2
'}
fHysteresis : LREAL :=1; // percentage determining how far below the trigger value the fault should be released
sDevName: STRING;
bVeto : BOOL:=FALSE; // This Fault will be will not trip the beam if the bVeto is TRUE
bAutoReset : BOOL:=TRUE;
END_VAR
VAR_IN_OUT
io_fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'instance-path'}
{attribute 'noinit'}
sPath: STRING;
bFAULT_OK:BOOL :=FALSE;
FFO : FB_FastFault :=(
i_Desc := 'Fault occurs when the temprature trip point is reached',
i_TypeCode := 16#f700);
rtRESET : R_TRIG;
ftFAULT : F_TRIG;
fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.MPS, nMinTimeViolationAcceptable:=10);
END_VAR
//Verify Hysteresis is between 1-100, Shouldn't be 0. shouldn't be a 100 either.
fHysteresis:= LIMIT(1, fHysteresis, 100);
// Evaluate the threshold trip point
IF (rPress >= fFaultThreshold) THEN
bFAULT_OK := FALSE;
ELSIF (rPress < (fFaultThreshold - fFaultThreshold*fHysteresis/100)) THEN
bFAULT_OK := TRUE;
END_IF
ACT_Logger();
(*FAST FAULT*)
FFO(i_xOK := bFAULT_OK OR bVeto,
i_xReset :=,
i_xAutoReset := bAutoReset,
i_DevName := sDevName,
io_fbFFHWO := io_fbFFHWO);
END_FUNCTION_BLOCK
ACTION ACT_Logger:
ftFAULT(CLK:= FFO.i_xOK);
rtRESET(CLK:=FFO.i_xOK);
IF(ftFAULT.Q) THEN
fbLogger(sMsg := 'Temp Threshold Fault, beam off', eSevr:=TcEventSeverity.Critical);
END_IF
IF(rtRESET.Q) THEN
fbLogger(sMsg := 'Temp Threshold Fault condition clear', eSevr:=TcEventSeverity.Info);
END_IF
END_ACTION
- Related:
FB_RateFromEPICS
FUNCTION_BLOCK FB_RateFromEPICS
(*
enum MPSBeamRates {
MPSRateInvalid = 0,
MPSRate0Hz = 1,
/* Previously used 2 and 3 for single shot and burst */
MPSRate1Hz = 4,
MPSRate10Hz = 5,
MPSRate30Hz = 6,
MPSRate60Hz = 7,
MPSRate120Hz = 8,
MPSRateUnknown = 9,
MPSRateSingleShot = 10,
MPSRateBurstMode = 11,
MPSRateBurstModeNotActive = 12,
MPSNumberOfBeamRates = 13,
MPSRateBurstInvalid = 14
}
*)
VAR_IN_OUT
BP : ST_BeamParams;
fbBYKIK_Rate : FB_LREALFromEPICS;
FFO : FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
xError : BOOL;
END_VAR
VAR
eMPSRate : E_MPSBeamRates;
ffRateReadBack : FB_FastFault := (
i_DevName := 'Arbiter',
i_Desc := 'Issue with rate readback from Accelerator. Gateway or EPICS connection. Must be fixed.',
i_TypeCode := 16#203,
i_xAutoReset:=True);
END_VAR
VAR CONSTANT
cFailSafeRate : UDINT := 1000001;
END_VAR
fbBYKIK_Rate();
IF fbBYKIK_Rate.bValid THEN
eMPSRate := LREAL_TO_UINT(fbBYKIK_Rate.fValue);
ELSE
eMPSRate := E_MPSBeamRates.MPSRateInvalid;
END_IF
CASE eMPSRate OF
MPSRateInvalid:
BP.nRate := cFailSafeRate;
MPSRate0Hz:
BP.nRate := 0;
MPSRate1Hz:
BP.nRate := 1;
MPSRate10Hz:
BP.nRate := 10;
MPSRate30Hz:
BP.nRate := 30;
MPSRate60Hz:
BP.nRate := 60;
MPSRate120Hz:
BP.nRate := 120;
MPSRateUnknown:
BP.nRate := cFailSafeRate;
ELSE
BP.nRate := cFailSafeRate;
END_CASE
ffRateReadback(i_xOK:=fbBYKIK_Rate.bValid, io_fbFFHWO:=FFO);
BP.xValid R= NOT fbBYKIK_Rate.bValid OR
eMPSRate = MPSRateUnknown OR
eMPSRate = MPSRateInvalid;
END_FUNCTION_BLOCK
FB_SafeBPCompare_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_SafeBPCompare_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
BeamOffIsSafe();
BPComparisonCheck();
END_FUNCTION_BLOCK
//Verify beam off is considered safe.
METHOD BeamOffIsSafe
VAR_INPUT
END_VAR
VAR
stBeamOn_MoreConservative : ST_BeamParams;
stBeamOff_LessConservative : ST_BeamParams;
stBeam0Rate : ST_BeamParams;
stBeam0BC : ST_BeamParams;
fb_BeamClassOutputs_BCD:FB_BeamClassOutputs_BCD;
END_VAR
stBeamOn_MoreConservative := F_SetBeamParams(
0, //0% transmission is more conservative than 50
16#FFFF_FFFF,
120, //rate
16#0001,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
stBeamOff_LessConservative:= F_SetBeamParams(
0.5, //50% transmission, less conservative, without 0-rate this beam parameter set would be less safe than above
16#FFFF_FFFF,
0, //rate, hence, more conservative
16#000F,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY); //0 beam rate means this bps is actually safer than BeamOn
// Set Machine Mode to NC
PMPS_GVL.stCurrentBeamParameters.nMachineMode := 0;
TEST('BeamOffIsSafeNC');
AssertTrue(
F_SafeBPCompare0Rate(stBeamOff_LessConservative, stBeamOn_MoreConservative),
'SafeBPCompare in NC does not think beam rate = 0 is safer than anything else.');
TEST_FINISHED();
TEST('BeamClass0IsNOTSafeNC');
stBeam0BC := F_SetBeamParams(
1, //0% transmission is more conservative than 50
16#FFFF_FFFF,
1000, //rate
16#0000,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
AssertFalse(
F_SafeBPCompare0Rate(stBeam0BC, stBeamOn_MoreConservative),
'SafeBPCompare beam class = 0 is NOT safer than anything else in NC mode.');
TEST_FINISHED();
//SC tests
stBeamOn_MoreConservative := F_SetBeamParams(
0, //0% transmission is more conservative than 50
16#FFFF_FFFF,
0, //rate
16#000F,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
stBeamOff_LessConservative:= F_SetBeamParams(
0.5, //50% transmission, less conservative, without 0-rate this beam parameter set would be less safe than above
16#FFFF_FFFF,
1000,
16#0000, //zero beam, hence, more conservative
PMPS_GVL.DUMMY_AUX_ATT_ARRAY); //0 beam rate means this bps is actually safer than BeamOn
// Set Machine Mode to SC
PMPS_GVL.stCurrentBeamParameters.nMachineMode := 1;
TEST('BeamOffIsSafeSC');
AssertTrue(
F_SafeBPCompare0Rate(stBeamOff_LessConservative, stBeamOn_MoreConservative),
'SafeBPCompare in SC mode does not think beam class = 0 is safer than anything else.');
TEST_FINISHED();
TEST('Beam0RateIsNOTSafeSC');
stBeam0Rate := F_SetBeamParams(
1, //0% transmission is more conservative than 50
16#FFFF_FFFF,
0, //rate
16#00FF,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
AssertFalse(
F_SafeBPCompare0Rate(stBeam0Rate, stBeamOn_MoreConservative),
'SafeBPCompare beam rate = 0 is NOT safer than anything else in SC mode.');
TEST_FINISHED();
// Set Machine Mode to Misconfigured
PMPS_GVL.stCurrentBeamParameters.nMachineMode := 2;
TEST('WrongMachineMode');
AssertFalse(
F_SafeBPCompare0Rate(stBeam0Rate, stBeam0BC),
'SafeBPCompare in Misconfigured mode does not think beam rate = 0 is safer than anything else.');
AssertFalse(
F_SafeBPCompare0Rate(stBeam0BC, stBeam0Rate),
'SafeBPCompare in Misconfiigured mode does not think beam class = 0 is safer than anything else.');
TEST_FINISHED();
END_METHOD
//Verify BP comparison remains logical.
{attribute 'no_check'}
METHOD BPComparisonCheck
VAR_INPUT
END_VAR
VAR
stSafer : ST_BeamParams;
stNotSoSafe : ST_BeamParams;
idx : UINT;
xResult : BOOL;
xFailedTest : BOOL; // To exit loops early
END_VAR
// Test in NC mode
PMPS_GVL.stCurrentBeamParameters.nMachineMode :=0;
TEST('BPComparisonTran');
//Attenuation
stSafer := F_SetBeamParams(
0.51, //
16#FFFFFFFF,
1,
16#0000,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
stNotSoSafe := F_SetBeamParams(
0.61, //
16#FFFFFFFF,
1,
16#0000,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
AssertTrue(
F_SafeBPCompare(stSafer, stNotSoSafe),
'Attenuation eval is broken (True)');
AssertFalse(
F_SafeBPCompare(stNotSoSafe, stSafer),
'Attenuation eval is broken (False)');
// Check at margin threshold
stSafer.nTran := stNotSoSafe.nTran*(PMPS_GVL.TRANS_SCALING_FACTOR + PMPS_PARAM.TRANS_MARGIN)/PMPS_GVL.TRANS_SCALING_FACTOR;
AssertTrue(
F_SafeBPCompare(stSafer, stNotSoSafe),
'Attenuation eval is broken: should be safe up to margin.');
stSafer.nTran := 1 + stNotSoSafe.nTran*(PMPS_GVL.TRANS_SCALING_FACTOR + PMPS_PARAM.TRANS_MARGIN)/PMPS_GVL.TRANS_SCALING_FACTOR;
AssertFalse(
F_SafeBPCompare(stSafer, stNotSoSafe),
'Attenuation eval is broken (False): should not be safe at all past margin');
TEST_FINISHED();
//Attenuator array
TEST('BPComparisonTranArray');
stSafer := F_SetBeamParams(
0, //
16#FFFFFFFF,
1,
16#0000,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
stNotSoSafe := F_SetBeamParams(
0, //
16#FFFFFFFF,
1,
16#000F,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
FOR idx:=1 TO PMPS_GVL.AUX_ATTENUATORS DO
stSafer.astAttenuators[idx].nTran := 0.00500;
stNotSoSafe.astAttenuators[idx].nTran := 0.00600;
xResult := F_SafeBPCompare(stSafer, stNotSoSafe);
AssertTrue(
xResult,
'Attenuation array eval is broken (True)');
xFailedTest S= NOT xResult;
xResult := F_SafeBPCompare(stNotSoSafe, stSafer);
AssertFalse(
xResult,
'Attenuation array eval is broken (False)');
xFailedTest S= xResult;
// Check at margin threshold
stSafer.astAttenuators[idx].nTran := stNotSoSafe.astAttenuators[idx].nTran*(PMPS_GVL.TRANS_SCALING_FACTOR + PMPS_PARAM.TRANS_MARGIN)/PMPS_GVL.TRANS_SCALING_FACTOR;
xResult := F_SafeBPCompare(stSafer, stNotSoSafe);
AssertTrue(
xResult,
'Attenuation eval is broken: should be safe up to margin.');
xFailedTest S= NOT xResult;
stSafer.astAttenuators[idx].nTran := 1 + stNotSoSafe.astAttenuators[idx].nTran*(PMPS_GVL.TRANS_SCALING_FACTOR + PMPS_PARAM.TRANS_MARGIN)/PMPS_GVL.TRANS_SCALING_FACTOR;
xResult := F_SafeBPCompare(stSafer, stNotSoSafe);
AssertFalse(
xResult,
'Attenuation eval is broken (False): should not be safe at all past margin');
xFailedTest S= xResult;
//////////////////////
IF xFailedTest THEN
stSafer.astAttenuators[idx].nTran := 0;
stNotSoSafe.astAttenuators[idx].nTran := 0;
xFailedTest := FALSE;
EXIT; // Exit this loop on the first failure.
END_IF
stSafer.astAttenuators[idx].nTran := 0;
stNotSoSafe.astAttenuators[idx].nTran := 0;
END_FOR
TEST_FINISHED();
stSafer.astAttenuators[idx].nTran := 0;
stNotSoSafe.astAttenuators[idx].nTran := 0;
//Aperture array
TEST('BPComparisonApertureArray');
FOR idx:=1 TO PMPS_GVL.MAX_APERTURES DO
// Height
stSafer.astApertures[idx].Height := 30; // narrower is safer
stNotSoSafe.astApertures[idx].Height := 50;
AssertTrue(
F_SafeBPCompare(stSafer, stNotSoSafe),
'Aperture array eval is broken on height (True)');
AssertFalse(
F_SafeBPCompare(stNotSoSafe, stSafer),
'Aperture array eval is broken on height (False)');
IF NOT F_SafeBPCompare(stSafer, stNotSoSafe) or F_SafeBPCompare(stNotSoSafe, stSafer) THEN
EXIT; // Exit this loop on the first failure.
END_IF
stSafer.astApertures[idx].Height := 0;
stNotSoSafe.astApertures[idx].Height := 0;
// Width
stSafer.astApertures[idx].Width := 30; // narrower is safer
stNotSoSafe.astApertures[idx].Width := 50;
AssertTrue(
F_SafeBPCompare(stSafer, stNotSoSafe),
'Aperture array eval is broken on width (True)');
AssertFalse(
F_SafeBPCompare(stNotSoSafe, stSafer),
'Aperture array eval is broken on width (False)');
IF NOT F_SafeBPCompare(stSafer, stNotSoSafe) or F_SafeBPCompare(stNotSoSafe, stSafer) THEN
EXIT; // Exit this loop on the first failure.
END_IF
stSafer.astApertures[idx].Width := 0;
stNotSoSafe.astApertures[idx].Width := 0;
END_FOR
TEST_FINISHED();
//Rate
TEST('BPComparisonRate');
stSafer := F_SetBeamParams(
0,
16#FFFF_FFFF,
1,
16#0000,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY); //
stNotSoSafe := F_SetBeamParams(
0,
16#FFFF_FFFF,
10,
16#0001,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY); //
AssertTrue(
F_SafeBPCompare(stSafer, stNotSoSafe),
'Rate eval is broken (True)');
AssertFalse(
F_SafeBPCompare(stNotSoSafe, stSafer),
'Rate eval is broken (False)');
TEST_FINISHED();
//Pulse energy
(*
TEST('BPComparisonPulseEnergy');
stSafer := F_SetBeamParams(
0,
1, //
16#FFFF,
1,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
stNotSoSafe := F_SetBeamParams(
0,
10, //
16#FFFF,
1,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
*)
AssertTrue(
F_SafeBPCompare(stSafer, stNotSoSafe),
'Pulse energy eval is broken (True)');
AssertFalse(
F_SafeBPCompare(stNotSoSafe, stSafer),
'Pulse energy eval is broken (False)');
TEST_FINISHED();
//Photon energy
//More extensive tests elsewhere
TEST('BPComparisonPhotonEnergy');
stSafer := F_SetBeamParams(
0,
2#0000_0000_0000_0000_0000_0000_0000_0010,
1,
16#0000,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
stNotSoSafe := F_SetBeamParams(
0,
2#0000_0000_0000_0000_0000_0000_0000_0110,
1,
16#0001,
PMPS_GVL.DUMMY_AUX_ATT_ARRAY);
AssertTrue(
F_SafeBPCompare(stSafer, stNotSoSafe),
'Photon energy eval is broken (True)');
AssertFalse(
F_SafeBPCompare(stNotSoSafe, stSafer),
'Photon energy eval is broken (False)');
TEST_FINISHED();
END_METHOD
FB_SolidAttenuator
FUNCTION_BLOCK FB_SolidAttenuator
VAR_INPUT
i_rRequestedAttenuation : REAL;
END_VAR
VAR_OUTPUT
q_xFault : BOOL;
q_rCurrentAttenuation : REAL;
END_VAR
VAR_IN_OUT
Arbiter : FB_Arbiter; //Higher level arbiter from which upstream attenuation can be requested
END_VAR
VAR
stAttenuator : ST_BinarySolidAttenuator;
nSearchStart: INT;
nArrayLength: INT;
axNewBladeStates : ARRAY [0..9] OF BOOL;
nTryIndex: INT;
rCurrentAccumulatedAttenuation: REAL;
rTryFilter: REAL;
rTryAttenuation: REAL;
rRequestedAttenuation: REAL;
END_VAR
//
//Assuming the blades are ordered thickest to thinnest
REPEAT
rTryAttenuation := rCurrentAccumulatedAttenuation + stAttenuator.axBlades[nTryIndex].rThickness;
IF rRequestedAttenuation >= rTryAttenuation THEN
rCurrentAccumulatedAttenuation := rTryAttenuation;
axNewBladeStates[nTryIndex] := TRUE;
ELSE
axNewBladeStates[nTryIndex] := FALSE;
END_IF
UNTIL
nTryIndex >= nArrayLength
END_REPEAT
END_FUNCTION_BLOCK
- Related:
FB_StopperWatcher
// Relays stopper state and sends a message when stopper state changes.
FUNCTION_BLOCK FB_StopperWatcher
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
stCurrentBP : ST_BeamParams;
END_VAR
VAR
i_StopperOutLS AT %I* : BOOL;
i_StopperInLS AT %I* : BOOL;
q_StopperOUT_Relay AT %Q* : BOOL;
q_StopperIN_Relay AT %Q* : BOOL;
Stopper : UINT;
StopperName : STRING;
{attribute 'instance-path'}
{attribute 'noinit'}
sPath : T_MaxString;
// Logging
fbLogMsg : FB_LogMessage := (
eSubSystem := E_Subsystem.MPS);
rtIn : R_TRIG;
rtOut : R_TRIG;
bInit: BOOL := TRUE;
END_VAR
IF bInit THEN
fbLogMsg.sJson := F_PMPS_JSON(StopperName, sPath, 1000);
bInit := FALSE;
END_IF
rtIn(CLK := i_StopperInLS);
rtOut(CLK := i_StopperOutLS);
IF rtIn.Q THEN
fbLogMsg.sMsg := CONCAT(StopperName, ' moved IN');
fbLogMsg();
ELSIF rtOut.Q THEN
fbLogMsg.sMsg := CONCAT(StopperName, ' moved OUT');
fbLogMsg();
END_IF
q_StopperOUT_Relay := i_StopperOutLS;
q_StopperIN_Relay := i_StopperInLS;
// Update current beam parameters
stCurrentBP.aVetoDevices[Stopper] := i_StopperInLS;
END_FUNCTION_BLOCK
- Related:
FB_SubsysToArb_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_SubsysToArb_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
BasicFunction();
VetoFunction();
END_FUNCTION_BLOCK
{attribute 'no_check'}
METHOD BasicFunction
VAR_INPUT
END_VAR
VAR
nId : DWORD := 1;
stReq : ST_BeamParams := (nTran:=12);
END_VAR
VAR_INST
fbArbiter : FB_Arbiter(1);
fbSubSysToArb : FB_SubSysToArbiter_IO; // Subsystem interface with beamline arbiter PLC
pBPC : POINTER TO ST_BeamParams_IO;
ioCurrentBP : ST_BeamParams_IO;
FFO : FB_HardwareFFOutput; // Dummy FFO not important for these tests
nCohortScratch : UDINT;
END_VAR
VAR CONSTANT
SysID : DWORD := 42;
END_VAR
// This simulates a synchronous cycle between the subsystem and arbiter PLC cycles. Ie. phase is locked, and cycle time is the same. This is not always the case, so these tests should be
// trusted with a grain of salt.
TEST('Add to arbiter');
IF NOT fbArbiter.CheckRequestInPool(nID) THEN
AssertTrue(fbArbiter.AddRequest(nId, stReq,'Device'), 'Arbiter returned false from AddRequest'); // some device asking its local arbiter for some beam parameters
END_IF
TEST_FINISHED();
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////
TEST('Initial Elevation');
AssertTrue(fbSubSysToArb.nRequestCohort = fbSubSysToArb.q_stRequestedBP.nCohortInt, 'ReqCohort and qBP Cohort do not match');
AssertFalse(fbArbiter.nActiveCohort <> 0, 'Arbiter indicates it is included in arbitration prematurely');
AssertTrue(fbSubSysToArb.nRequestCohort <> 0, 'Cohort index should move to 1 with first request');
TEST_FINISHED_NAMED('Initial Elevation');
nCohortScratch := fbSubSysToArb.nRequestCohort;
//Ethercat transfer simulation
// Transfer of requested BP to arbiter PLC
// pBPR := ADR(fbArbToSubSys.i_RequestedBP);
// pBPR^ := fbSubSysToArb.q_stRequestedBP;
ioCurrentBP := fbSubSysToArb.q_stRequestedBP;
// NOTE: Setting the returning cohort number to something less than the request cohort number
// will prevent the sub system arbiter FB from considering itself active in arbitration.
// The next set of tests verify this.
ioCurrentBP.nCohortInt := 0;
// Transfer of current BP to sub system PLC
pBPC := ADR(fbSubSysToArb.i_stCurrentBP);
pBPC^ := ioCurrentBP;
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////
TEST('Not yet active in arbitration');
AssertTrue(fbSubSysToArb.nActiveCohort = 0, 'Active cohort should still be zero, per ioCurrentBP.nCohortInt setting above.');
AssertFalse(fbArbiter.nActiveCohort <> 0, 'Arbiter indicates it is included in arbitration prematurely, checkrequest might be broken');
AssertTrue(fbSubSysToArb.nRequestCohort = nCohortScratch, 'Cohort index should not have changed because there have been no additional requests added to the arbiter');
TEST_FINISHED_NAMED('Not yet active in arbitration');
//Ethercat transfer simulation
// Transfer of requested BP to arbiter PLC
// pBPR := ADR(fbArbToSubSys.i_RequestedBP);
// pBPR^ := fbSubSysToArb.q_stRequestedBP;
ioCurrentBP := fbSubSysToArb.q_stRequestedBP;
// NOTE: Now we simulate the FB_ArbiterToSubSys_IO updating the cohort number to the same value as the request cohort, indicating that cohort has been included
ioCurrentBP.nCohortInt := 2;
// Transfer of current BP to sub system PLC
pBPC := ADR(fbSubSysToArb.i_stCurrentBP);
pBPC^ := ioCurrentBP;
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////
TEST('Active in arbitration');
AssertTrue(fbSubSysToArb.nActiveCohort = fbSubSysToArb.nRequestCohort, 'Active cohort and request cohort do not match');
AssertTrue(fbArbiter.nActiveCohort = 1, 'Arbiter does not indicate it is included in higher arbitration');
TEST_FINISHED_NAMED('Active in arbitration');
END_METHOD
METHOD VetoFunction
VAR_INPUT
END_VAR
VAR
nId : DWORD := 1;
stReq : ST_BeamParams := (nTran:=12);
END_VAR
VAR_INST
fbArbiter : FB_Arbiter(1);
fbSubSysToArb : FB_SubSysToArbiter_IO; // Subsystem interface with beamline arbiter PLC
fbArbToSubSys : FB_ArbiterToSubSys_IO;
pBPC : POINTER TO ST_BeamParams_IO;
pBPR : POINTER TO ST_BeamParams_IO;
ioCurrentBP : ST_BeamParams_IO;
FFO : FB_HardwareFFOutput; // Dummy FFO not important for these tests
END_VAR
VAR CONSTANT
SysID : DWORD := 42;
END_VAR
// This simulates a synchronous cycle between the subsystem and arbiter PLC cycles. Ie. phase is locked, and cycle time is the same. This is not always the case, so these tests should be
// trusted with a grain of salt.
fbArbiter.AddRequest(nId, stReq,'Device'); // some device asking its local arbiter for some beam parameters
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO);
//END of sub system cycle
///////////////////////////////////////
TEST('Request propagated');
AssertTrue(fbSubSysToArb.q_stRequestedBP.nTran = stReq.nTran, 'Request not propagated');
TEST_FINISHED();
// Veto activated
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=TRUE);
//END of sub system cycle
///////////////////////////////////////
// Veto activated
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=TRUE);
//END of sub system cycle
///////////////////////////////////////
TEST('Request sustained');
AssertTrue(fbSubSysToArb.q_stRequestedBP.nTran = PMPS_GVL.cstFullBeam.nTran, 'Request still being propagated');
AssertTrue(fbArbiter.CheckRequest(nId), 'Request still not valid');
TEST_FINISHED();
// Veto activated
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=FALSE);
//END of sub system cycle
///////////////////////////////////////
// Veto activated
///////////////////////////////////////
//Sub system cycle
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=FALSE);
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=FALSE);
fbSubSysToArb(Arbiter := fbArbiter, fbFFHWO := FFO, i_bVeto:=FALSE);
//END of sub system cycle
///////////////////////////////////////
TEST('Request restored');
AssertTrue(fbSubSysToArb.q_stRequestedBP.nTran = stReq.nTran, 'Request still being sustained');
TEST_FINISHED();
END_METHOD
FB_SubSysToArbiter_IO
// Use on a subsystem PLC to request from the arbiter
// Run at the top of your cycle to receive the latest BP
FUNCTION_BLOCK FB_SubSysToArbiter_IO IMPLEMENTS I_HigherAuthority
VAR_INPUT
Reset : BOOL; // Fast fault reset
sName : STRING := 'SubSysToArbiter';
i_bVeto : BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
Arbiter : FB_Arbiter;
fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR
{attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^IO Inputs^CurrentBP'}
i_stCurrentBP AT %I* : ST_BeamParams_IO;
{attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^IO Outputs^RequestedBP'}
q_stRequestedBP AT %Q* : ST_BeamParams_IO;
{attribute 'pytmc' := 'pv: TxPDO_toggle
io: i'}
{attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^SYNC Inputs^TxPDO toggle'}
xTxPDO_toggle AT %I* : BIT;
{attribute 'pytmc' := 'pv: TxPDO_state
io: i'}
{attribute 'TcLinkTo' := 'TIIB[PMPS_PRE]^SYNC Inputs^TxPDO state'}
xTxPDO_state AT %I* : BIT;
// Fast faults
ffPMPSIO_Disconnect : FB_FastFault := (i_Desc:='Arbiter network interface disconnected or not OP',i_DevName := sName);
// A request is not considered included until the active cohort number is >= the request cohort number.
{attribute 'pytmc' := 'pv: RequestCohort
io: i'}
nRequestCohort : UDINT := 0; // Request cohort
{attribute 'pytmc' := 'pv: ActiveCohort
io: i'}
nActiveCohort : UDINT := 0; // Active cohort, updated by incoming BP from arbiter PLC, in the ElevateRequest arbiter call
fbVetoArb : FB_VetoArbiter;
fbLog : FB_LogMessage;
END_VAR
//Receiving current BP state
PMPS_GVL.stCurrentBeamParameters := IO_TO_BP(i_stCurrentBP);
PMPS_GVL.stCurrentBeamParameters.xValidToggle := xTxPDO_toggle; // This line must follow the one above. Sequence is important.
// Forwarding BP request for the subsystem
fbVetoArb.bVeto := i_bVeto;
fbVetoArb(HigherAuthority:=THIS^, LowerAuthority:=Arbiter, FFO:=fbFFHWO);
//Broadcasting current request
PMPS_GVL.stRequestedBeamParameters := IO_TO_BP(q_stRequestedBP);
q_stRequestedBP.xValid := TRUE; // This is set and held true here every cycle to prove the PLC on this side is still running
ffPMPSIO_Disconnect(
i_xOK := xTxPDO_state = 0,
io_fbFFHWO := fbFFHWO,
i_xReset := Reset,
i_DevName := sName,
i_TypeCode := 6,
i_xVetoable := FALSE
);
END_FUNCTION_BLOCK
METHOD CheckRequest : BOOL
VAR_INPUT
nReqID : DWORD;
END_VAR
VAR_INST
xFirstTime : BOOL := TRUE;
nId : DWORD;
END_VAR
// Check this id matches what we've seen before
IF xFirstTime THEN
nId := nReqId;
ELSIF nId <> nReqId THEN
fbLog(sMsg := 'SubSysToArbiter Check mismatched with a different ID', eSevr := TcEventSeverity.Error);
RETURN;
END_IF
nActiveCohort := ULINT_TO_UDINT(i_stCurrentBP.nCohortInt);
// i_stCurrentBP.nCohortInt is incremented by the FB_ArbiterToSubSys block on the other side of the EL669* interface
CheckRequest := nRequestCohort <= nActiveCohort;
END_METHOD
METHOD RemoveRequest : BOOL
VAR_INPUT
nReqID : DWORD; //StateID to remove
END_VAR
// Update internal BP request struct
q_stRequestedBP := BP_TO_IO(PMPS_GVL.cstFullBeam);
nRequestCohort := nRequestCohort + 1;
// Mark the current cohort id
q_stRequestedBP.nCohortInt := nRequestCohort;
RemoveRequest := TRUE;
END_METHOD
METHOD RequestBP : BOOL
VAR_INPUT
(*StateID of state requesting beam parameter set*)
nReqID : DWORD;
(*Requested beam params*)
stReqBP : ST_BeamParams;
END_VAR
// Check the request is coming from the same source we're used to
// Update internal BP request struct
q_stRequestedBP := BP_TO_IO(stReqBP);
nRequestCohort := nRequestCohort + 1;
// Mark the current cohort id
q_stRequestedBP.nCohortInt := nRequestCohort;
RequestBP := TRUE;
END_METHOD
FB_SXU
FUNCTION_BLOCK FB_SXU IMPLEMENTS I_UndulatorComplex
VAR_INPUT
fbElectronEnergy : REFERENCE TO FB_LREALFromEPICS;
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: CurrentPhotonEnergy
io: i
field: DESC Calculated current photon energy
field: PREC 3
field: EGU eV
'}
fCurrentPhotonEnergy : LREAL;
{attribute 'pytmc' := '
pv: TargetPhotonEnergy
io: i
field: DESC Calculated desired photon energy
field: PREC 3
field: EGU eV
'}
fTargetPhotonEnergy : LREAL;
{attribute 'pytmc' := '
pv: SeedUndulatorNumber
io: i
field: DESC Seed undulator number
'}
nSeedUndulator : UDINT; // Set to zero when no undulators are active
{attribute 'pytmc' := '
pv: TargetSeedUndulatorNumber
io: i
field: DESC Seed undulator number for target K
'}
nTargetSeedUndulator : UDINT; // Set to zero when no undulators are active
END_VAR
VAR
// From lcls-srv01: grep -e KDes /u1/lcls/epics/ioc/data/sioc-unds-uc*/iocInfo/IOC.pvlist |sort
{attribute 'pytmc' := 'pv: 26; link: 2650:'}
fbSegment_26 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 27; link: 2750:'}
fbSegment_27 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 28; link: 2850:'}
fbSegment_28 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 29; link: 2950:'}
fbSegment_29 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 30; link: 3050:'}
fbSegment_30 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 31; link: 3150:'}
fbSegment_31 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 32; link: 3250:'}
fbSegment_32 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 33; link: 3350:'}
fbSegment_33 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 34; link: 3450:'}
fbSegment_34 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 36; link: 3650:'}
fbSegment_36 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 37; link: 3750:'}
fbSegment_37 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 38; link: 3850:'}
fbSegment_38 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 39; link: 3950:'}
fbSegment_39 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 40; link: 4050:'}
fbSegment_40 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 41; link: 4150:'}
fbSegment_41 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 42; link: 4250:'}
fbSegment_42 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 43; link: 4350:'}
fbSegment_43 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 44; link: 4450:'}
fbSegment_44 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 45; link: 4550:'}
fbSegment_45 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 46; link: 4650:'}
fbSegment_46 : FB_UndulatorSegment;
{attribute 'pytmc' := 'pv: 47; link: 4750:'}
fbSegment_47 : FB_UndulatorSegment;
fbSegment : ARRAY [iLowBound..iHighBound] OF POINTER TO FB_UndulatorSegment;
fbCurrentSegment : REFERENCE TO FB_UndulatorSegment;
iIndex : UDINT;
bInitialized : BOOL := FALSE;
END_VAR
VAR CONSTANT
{attribute 'pytmc' := '
pv: FirstSegment
io: i'}
iLowBound : UDINT := 26;
{attribute 'pytmc' := '
pv: LastSegment
io: i'}
iHighBound : UDINT := 47;
{attribute 'pytmc' := '
pv: Period
io: i
field: EGU mm
'}
fPeriod_mm : LREAL := 39.0;
{attribute 'pytmc' := '
pv: LowK
io: i
'}
fLowK : LREAL := 1.5;
{attribute 'pytmc' := '
pv: HiK
io: i
'}
fHiK : LREAL := 5.7;
END_VAR
IF NOT bInitialized THEN
Init();
END_IF
UndAdrUpdate();
nSeedUndulator := 0;
nTargetSeedUndulator := 0;
FOR iIndex := iLowBound TO iHighBound DO
IF fbSegment[iIndex] <> 0 THEN
fbCurrentSegment REF= fbSegment[iIndex]^;
fbCurrentSegment(fbElectronEnergy:=fbElectronEnergy);
//Mark the seed undulator, first undulator operating within K bounds
IF fbCurrentSegment.xActive AND nSeedUndulator = 0 THEN
nSeedUndulator := iIndex;
fCurrentPhotonEnergy := fbCurrentSegment.fPhotonEnergyAct;
END_IF
IF fbCurrentSegment.xTargetActive AND nTargetSeedUndulator = 0 THEN
nTargetSeedUndulator := iIndex;
fTargetPhotonEnergy := fbCurrentSegment.fPhotonEnergyDes;
END_IF
END_IF
END_FOR
IF nSeedUndulator = 0 THEN
fCurrentPhotonEnergy := 0;
END_IF
IF nTargetSeedUndulator = 0 THEN
fTargetPhotonEnergy := 0;
END_IF
END_FUNCTION_BLOCK
ACTION Init:
UndAdrUpdate();
FOR iIndex := iLowBound TO iHighBound DO
IF fbSegment[iIndex] <> 0 THEN
fbCurrentSegment REF= fbSegment[iIndex]^;
fbCurrentSegment.fPeriod_mm := fPeriod_mm;
fbCurrentSegment.fLowK := fLowK;
fbCurrentSegment.fHiK := fHiK;
END_IF
END_FOR
bInitialized := TRUE;
END_ACTION
ACTION UndAdrUpdate:
fbSegment[26] := ADR(fbSegment_26);
fbSegment[27] := ADR(fbSegment_27);
fbSegment[28] := ADR(fbSegment_28);
fbSegment[29] := ADR(fbSegment_29);
fbSegment[30] := ADR(fbSegment_30);
fbSegment[31] := ADR(fbSegment_31);
fbSegment[32] := ADR(fbSegment_32);
fbSegment[33] := ADR(fbSegment_33);
fbSegment[34] := ADR(fbSegment_34);
fbSegment[35] := 0;
fbSegment[36] := ADR(fbSegment_36);
fbSegment[37] := ADR(fbSegment_37);
fbSegment[38] := ADR(fbSegment_38);
fbSegment[39] := ADR(fbSegment_39);
fbSegment[40] := ADR(fbSegment_40);
fbSegment[41] := ADR(fbSegment_41);
fbSegment[42] := ADR(fbSegment_42);
fbSegment[43] := ADR(fbSegment_43);
fbSegment[44] := ADR(fbSegment_44);
fbSegment[45] := ADR(fbSegment_45);
fbSegment[46] := ADR(fbSegment_46);
fbSegment[47] := ADR(fbSegment_47);
END_ACTION
PROPERTY rCurrentPhotonEnergy : REAL
VAR
END_VAR
rCurrentPhotonEnergy := fCurrentPhotonEnergy;
END_PROPERTY
PROPERTY rTargetPhotonEnergy : REAL
VAR
END_VAR
rTargetPhotonEnergy := fTargetPhotonEnergy;
END_PROPERTY
- Related:
FB_TempSensor_FFO
FUNCTION_BLOCK FB_TempSensor_FFO EXTENDS FB_TempSensor
VAR_INPUT
{attribute 'pytmc' := '
pv: FAULT_SP
io: input
field: EGU C
field: PREC 2
'}
fFaultThreshold : LREAL; //Faults when the threshold is reached. Trigger value.
{attribute 'pytmc' := '
pv: FAULT_SP_HYS
io: input
field: EGU %
field: PREC 2
'}
fHysteresis : LREAL :=1; // percentage determining how far below the trigger value the fault should be released
sDevName: STRING;
bVeto : BOOL:=FALSE; // This Fault will be will not trip the beam if the bVeto is TRUE
bAutoReset : BOOL:=TRUE;
END_VAR
VAR_IN_OUT
io_fbFFHWO : FB_HardwareFFOutput;
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'instance-path'}
{attribute 'noinit'}
sPath: STRING;
bFAULT_OK:BOOL :=FALSE;
FFO : FB_FastFault :=(
i_Desc := 'Fault occurs when the temprature trip point is reached',
i_TypeCode := 16#f700);
rtRESET : R_TRIG;
ftFAULT : F_TRIG;
ftConnected : F_TRIG;
fbLogger : FB_LogMessage := (eSubsystem:=E_SubSystem.MPS, nMinTimeViolationAcceptable:=10);
END_VAR
// The manual states that we are disconnected if we are both overrange and in an error state
bConnected := NOT (bOverrange AND bError);
fTemp := INT_TO_LREAL(iRaw) * fResolution;
//Verify Hysteresis is between 1-100, Shouldn't be 0. shouldn't be a 100 either.
fHysteresis:= LIMIT(1, fHysteresis, 100);
// Evaluate the threshold trip point
IF (fTemp >= fFaultThreshold) THEN
bFAULT_OK := FALSE;
ELSIF (fTemp < (fFaultThreshold - fFaultThreshold*fHysteresis/100)) THEN
bFAULT_OK := TRUE;
END_IF
bFAULT_OK R= NOT bConnected;
ACT_Logger();
(*FAST FAULT*)
FFO(i_xOK := bFAULT_OK OR bVeto,
i_xReset :=,
i_xAutoReset := bAutoReset,
i_DevName := sDevName,
io_fbFFHWO := io_fbFFHWO);
END_FUNCTION_BLOCK
ACTION ACT_Logger:
ftFAULT(CLK:= FFO.i_xOK);
rtRESET(CLK:=FFO.i_xOK);
ftConnected(CLK:= bConnected);
IF(ftConnected.Q) THEN
fbLogger(sMsg := 'Sensor Connection Fault, beam off', eSevr:=TcEventSeverity.Critical);
END_IF
IF(ftFAULT.Q) THEN
fbLogger(sMsg := 'Temp Threshold Fault, beam off', eSevr:=TcEventSeverity.Critical);
END_IF
IF(rtRESET.Q) THEN
fbLogger(sMsg := 'Temp Threshold Fault condition clear', eSevr:=TcEventSeverity.Info);
END_IF
END_ACTION
- Related:
FB_UndulatorSegment
FUNCTION_BLOCK FB_UndulatorSegment
VAR_INPUT
(* Undulator period in millimeters, to be set by subclasses *)
fPeriod_mm : LREAL := 1.0;
fbElectronEnergy : REFERENCE TO FB_LREALFromEPICS;
fLowK : LREAL := 0;
fHiK : LREAL := 6;
fKRangeHyst : LREAL := 0.01;
END_VAR
VAR_OUTPUT
{attribute 'pytmc' := '
pv: eVAct
field: DESC Calculated photon energy
field: PREC 3
field: EGU eV
'}
fPhotonEnergyAct : LREAL;
{attribute 'pytmc' := '
pv: eVDes
field: DESC Calculated desired photon energy
field: PREC 3
field: EGU eV
'}
fPhotonEnergyDes : LREAL;
{attribute 'pytmc' := '
pv: Active
io: i
field: DESC Undulator is considered active
'}
xActive : BOOL; // Undulator is considered active
{attribute 'pytmc' := '
pv: TargetActive
io: i
field: DESC Target K would make und active
'}
xTargetActive : BOOL; // Undulator is considered active at this target
{attribute 'pytmc' := '
pv: KAct
io: i
field: DESC Current K
'}
fKAct : LREAL;
{attribute 'pytmc' := '
pv: KDes
io: i
field: DESC Target K
'}
fKDes : LREAL;
{attribute 'pytmc' := '
pv: KActValid
io: i
field: DESC Current K Readback Valid
'}
bKActValid : BOOL;
{attribute 'pytmc' := '
pv: KDesValid
io: i
field: DESC Target K Readback Valid
'}
bKDesValid : BOOL;
END_VAR
VAR
{attribute 'pytmc' := '
pv: KDes
link: KDes
'}
fbKDesired : FB_LREALFromEPICS;
{attribute 'pytmc' := '
pv: KAct
link: KAct
'}
fbKActual : FB_LREALFromEPICS;
END_VAR
fbKDesired();
fbKActual();
fKAct := fbKActual.fValue;
bKActValid := fbKActual.bValid;
fKDes := fbKDesired.fValue;
bKDesValid := fbKDesired.bvalid;
IF __ISVALIDREF(fbElectronEnergy) THEN
IF fbKActual.bValid AND fbElectronEnergy.bValid THEN
fPhotonEnergyAct := F_CalculatePhotonEnergy(
fElectronEnergy_GeV:=fbElectronEnergy.fValue,
fUndulatorPeriod_mm:=fPeriod_mm,
fUndulatorStrength:=fbKActual.fValue
);
//Set this undulator active if actual K is within operational range
xActive S= fLowK <= fbKActual.fValue AND fbKActual.fValue <= fHiK AND
(fbKActual.bValid AND fbKActual.bValid);
xActive R= (fLowK - fKRangeHyst) > fbKActual.fValue OR fbKActual.fValue > (fHiK + fKRangeHyst) OR
(NOT fbKActual.bValid OR NOT fbKActual.bValid);
//Set this undulator active if target K is within operational range
xTargetActive S= fLowK <= fbKDesired.fValue AND fbKDesired.fValue <= fHiK AND
(fbKDesired.bValid AND fbKDesired.bValid);
xTargetActive R= (fLowK - fKRangeHyst) > fbKDesired.fValue OR fbKDesired.fValue > (fHiK + fKRangeHyst) OR
(NOT fbKDesired.bValid OR NOT fbKDesired.bValid);
END_IF
IF fbKDesired.bValid AND fbElectronEnergy.bValid THEN
fPhotonEnergyDes := F_CalculatePhotonEnergy(
fElectronEnergy_GeV:=fbElectronEnergy.fValue,
fUndulatorPeriod_mm:=fPeriod_mm,
fUndulatorStrength:=fbKDesired.fValue
);
END_IF
END_IF
END_FUNCTION_BLOCK
- Related:
FB_VetoArbiter
FUNCTION_BLOCK FB_VetoArbiter IMPLEMENTS I_HigherAuthority
VAR_INPUT
bVeto : BOOL := FALSE; // Rising edge clears request, hold true to veto continuously, falling edge restores request
HigherAuthority : I_HigherAuthority; // Typically connected to a higher-level arbiter.
LowerAuthority : I_LowerAuthority; // Lower authority to be vetoed
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
FFO : FB_HardwareFFOutput; // This should be the FFO upstream of the veto device
END_VAR
VAR
ffKeepItSecretKeepItSafe : FB_FastFault := (
i_xAutoReset := TRUE,
i_Desc := 'Holds beam off until request is back in arbitration',
i_TypeCode := 200,
i_xVetoable := TRUE
);
stStandbyBP : ST_BeamParams;
rtVeto : R_TRIG;
ftVeto : F_TRIG;
END_VAR
rtVeto(CLK:=bVeto);
ftVeto(CLK:=bVeto);
IF rtVeto.Q THEN
HigherAuthority.RemoveRequest(LowerAuthority.nLowerAuthorityID);
HigherAuthority.RequestBP(LowerAuthority.nLowerAuthorityID, PMPS_GVL.cstFullBeam);
ELSIF ftVeto.Q THEN
HigherAuthority.RemoveRequest(LowerAuthority.nLowerAuthorityID);
HigherAuthority.RequestBP(LowerAuthority.nLowerAuthorityID, stStandbyBP);
END_IF
LowerAuthority.ElevateRequest(THIS^);
//Fast fault that holds beam off until the request is added back into the system
// when bVeto goes false.
ffKeepItSecretKeepItSafe.i_xOK := HigherAuthority.CheckRequest(LowerAuthority.nLowerAuthorityID) OR bVeto;
ffKeepItSecretKeepItSafe(io_fbFFHWO:=FFO);
END_FUNCTION_BLOCK
METHOD CheckRequest : BOOL
VAR_INPUT
nReqID : DWORD;
END_VAR
IF bVeto THEN
CheckRequest := TRUE;
ELSE
CheckRequest := HigherAuthority.CheckRequest(nReqID);
END_IF
END_METHOD
METHOD RemoveRequest : BOOL
VAR_INPUT
(*StateID to remove*)
nReqID : DWORD;
END_VAR
IF bVeto THEN
RemoveRequest := TRUE;
ELSE
RemoveRequest := HigherAuthority.RemoveRequest(nReqID);
END_IF
END_METHOD
METHOD RequestBP : BOOL
VAR_INPUT
(*StateID of state requesting beam parameter set*)
nReqID : DWORD;
(*Requested beam params*)
stReqBP : ST_BeamParams;
END_VAR
IF NOT bVeto THEN
// Pass request along to higher authority
RequestBP := HigherAuthority.RequestBP(nReqID, stReqBP);
ELSE
RequestBP := TRUE;
END_IF
IF RequestBP THEN
stStandbyBP := stReqBP;
END_IF
END_METHOD
FB_VetoArbiter_Test
{attribute 'call_after_init'}
FUNCTION_BLOCK FB_VetoArbiter_Test EXTENDS TcUnit.FB_TestSuite
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
VAR CONSTANT
ArbID : DWORD := 1;
HigherArbID : DWORD := 2;
END_VAR
VetoArbiter();
END_FUNCTION_BLOCK
METHOD VetoArbiter
VAR_INPUT
END_VAR
VAR
nId : DWORD := 1;
nId2 : DWORD := 10;
stReq : ST_BeamParams := (nTran:=0.12);
stReq2 : ST_BeamParams := (nTran:=0.10);
END_VAR
VAR_INST
fbArbiter : FB_Arbiter(1);
fbHigherArb : FB_Arbiter(2);
ArbBP : ST_BeamParams:=(nTran:=1);
VetoArb : FB_VetoArbiter;
FFO : FB_HardwareFFOutput;
fbHA : FB_DummyHA;
END_VAR
TEST('VetoNotIn');
fbArbiter.AddRequest(nReqID:=nId, stReqBP:=stReq, sDevName :='Device');
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb,
LowerAuthority:=fbArbiter,
FFO:=FFO);
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb,
LowerAuthority:=fbArbiter,
FFO:=FFO);
AssertTrue(fbHigherArb.q_stBeamParams.nTran = stReq.nTran, 'We should see the transmission number here, veto device is not in');
TEST_FINISHED();
TEST('VetoIn');
// Veto device is in
// This should effectively remove the fbArbiter request from the higher arbiter pool
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := TRUE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
LowerAuthority:=fbArbiter,
FFO:=FFO);
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := TRUE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
LowerAuthority:=fbArbiter,
FFO:=FFO);
AssertTrue(fbHigherArb.q_stBeamParams.nTran = PMPS_GVL.cstFullBeam.nTran, 'Veto device is in, trans should be restored to 100');
AssertTrue(fbArbiter.CheckRequest(nId), 'Request should still be considered valid (1)');
AssertFalse(fbHigherArb.CheckRequest(ArbID), 'Lower arb request should be gone from the higher pool');
TEST_FINISHED();
TEST('AnotherRequest');
// Another request is added, should be approved immediately, and with no effect on the final set
fbArbiter.RequestBP(nReqID := nId2, stReqBP:=stReq2);
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := TRUE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
LowerAuthority:=fbArbiter,
FFO:=FFO);
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := TRUE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
LowerAuthority:=fbArbiter,
FFO:=FFO);
AssertTrue(ArbBP.nTran = PMPS_GVL.cstFullBeam.nTran, 'Veto device remained in, trans should still be 100');
AssertTrue(fbArbiter.CheckRequest(nId), 'Request should still be considered valid (2)');
AssertTrue(fbArbiter.CheckRequest(nId2), '2nd Request should be considered valid');
TEST_FINISHED();
TEST('RemoveVeto');
// Removal of veto device, should produce a fault
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
LowerAuthority:=fbArbiter,
FFO:=FFO);
AssertFalse(VetoArb.ffKeepItSecretKeepItSafe.i_xOK, 'Should produce a fast fault at this point because the request is not yet back in the pool');
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
LowerAuthority:=fbArbiter,
FFO:=FFO);
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
LowerAuthority:=fbArbiter,
FFO:=FFO);
//////////////////// Cycle
fbHigherArb.ElevateRequest(fbHA);
VetoArb.bVeto := FALSE;
VetoArb(HigherAuthority:=fbHigherArb, // Veto removed
LowerAuthority:=fbArbiter,
FFO:=FFO);
AssertTrue(VetoArb.ffKeepItSecretKeepItSafe.i_xOK, 'Fault should be gone.');
TEST_FINISHED();
END_METHOD
FB_VetoDevice
// Relays veto device state, updates current BP and sends a message when veto state changes.
FUNCTION_BLOCK FB_VetoDevice
VAR_INPUT
i_bIn : BOOL;
i_bOut : BOOL;
END_VAR
VAR_OUTPUT
q_bIn : BOOL;
q_bOut : BOOL;
END_VAR
VAR_IN_OUT
stCurrentBP : ST_BeamParams;
END_VAR
VAR
VetoDevice_IN : UINT := PMPS_GVL.MAX_VETO_DEVICES; // Veto device state array index
VetoDevice_OUT : UINT := PMPS_GVL.MAX_VETO_DEVICES; // Veto device state array index
VetoDeviceName : STRING;
{attribute 'instance-path'}
{attribute 'noinit'}
sPath : T_MaxString;
// Logging
fbLogMsg : FB_LogMessage := (
eSubSystem := E_Subsystem.MPS);
rtIn : R_TRIG;
rtOut : R_TRIG;
////////////////////////////////
bInit: BOOL := TRUE;
END_VAR
IF bInit THEN
fbLogMsg.sJson := F_PMPS_JSON(VetoDeviceName, sPath, 1000);
bInit := FALSE;
END_IF
// Log
/////////////////////////////
rtIn(CLK := i_bIn);
rtOut(CLK := i_bOut);
IF rtIn.Q THEN
fbLogMsg.sMsg := CONCAT(VetoDeviceName, ' moved IN');
fbLogMsg();
ELSIF rtOut.Q THEN
fbLogMsg.sMsg := CONCAT(VetoDeviceName, ' moved OUT');
fbLogMsg();
END_IF
// Relay
///////////////////////
q_bIn := i_bIn;
q_bOut := i_bOut;
// Update current beam parameters
/////////////////////////////////////
stCurrentBP.aVetoDevices[VetoDevice_IN] := i_bIn;
stCurrentBP.aVetoDevices[VetoDevice_OUT] := i_bOut;
END_FUNCTION_BLOCK
- Related:
IO_TO_APT
FUNCTION IO_TO_APT : ST_PMPS_Aperture
VAR_INPUT
IO : ST_PMPS_Aperture_IO;
END_VAR
VAR
END_VAR
IO_TO_APT.Height := IO.Height;
IO_TO_APT.Width := IO.Width;
IO_TO_APT.xOK := IO.xOK;
END_FUNCTION
- Related:
IO_TO_ATT
FUNCTION IO_TO_ATT : ST_PMPS_Attenuator
VAR_INPUT
IO : ST_PMPS_Attenuator_IO;
END_VAR
VAR
END_VAR
IO_TO_ATT.nTran := IO.nTran;
IO_TO_ATT.xAttOK := IO.xAttOK;
END_FUNCTION
- Related:
IO_TO_BP
{attribute 'no_check'}
FUNCTION IO_TO_BP : ST_BeamParams
VAR_INPUT
IO : ST_BeamParams_IO;
END_VAR
VAR
idx : UINT;
END_VAR
FOR idx := 1 TO PMPS_GVL.AUX_ATTENUATORS DO
IO_TO_BP.astAttenuators[idx] := IO_TO_ATT(IO.astAttenuators[idx]);
END_FOR
FOR idx := 1 TO PMPS_GVL.MAX_APERTURES DO
IO_TO_BP.astApertures[idx] := IO_TO_APT(IO.astApertures[idx]);
END_FOR
IO_TO_BP.aVetoDevices := IO.aVetoDevices;
IO_TO_BP.nTran := IO.nTran;
IO_TO_BP.nCohortInt := ULINT_TO_UDINT(IO.nCohortInt);
IO_TO_BP.neVRange := IO.neVRange;
IO_TO_BP.neV := IO.neV;
IO_TO_BP.nBCRange := IO.nBCRange;
IO_TO_BP.nBeamClass := IO.nBeamClass;
IO_TO_BP.nMachineMode := IO.nMachineMode;
IO_TO_BP.nRate := IO.nRate;
IO_TO_BP.xValid := IO.xValid;
IO_TO_BP.xValidToggle := IO.xValidToggle;
END_FUNCTION
- Related:
MAIN
PROGRAM MAIN
VAR
fbSetPERanges : PE_Ranges;
fbDiffBPTest : FB_DiffBP_Test;
fbBPTMTest : FB_BPTM_Test;
fbSafeBPCompareTest : FB_SafeBPCompare_Test;
////fbevWithinSpecTest : FB_evWithinSpec_Test;---
////fbPEWTest : FB_PhotonEnergyWatcher_Test;---
fbFFTest : FB_FastFault_Test;
fbArbiterTest : FB_Arbiter_Test;
fbVetoArbiterTest : FB_VetoArbiter_Test;
fbevRangeCalcTest : FB_evRangeCalculator_Test;
fbSubSysToArbTest : FB_SubsysToArb_Test;
fbArbToSubSysTest : FB_ArbToSubsys_Test;
END_VAR
TcUnit.RUN();
END_PROGRAM
PE_Ranges
// Does nothing other than set the gvl for photon energy bitmask to one of two constants, K or L.
// Workaround for compile defines not fully working for libraries at the time of writing this.
// Otherwise I would have just used the compile define in the GVL declaration.
FUNCTION_BLOCK PE_Ranges
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR
END_FUNCTION_BLOCK
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
END_VAR
{IF defined (L)}
PMPS_GVL.g_areVBoundaries := PMPS_GVL.g_areVBoundariesL;
{ELSIF defined (K)}
PMPS_GVL.g_areVBoundaries := PMPS_GVL.g_areVBoundariesK;
{END_IF}
END_METHOD
- Related: