DUTs ---- E_DeviceState ^^^^^^^^^^^^^ :: {attribute 'qualified_only'} TYPE E_DeviceState : ( INVALID := 0, INIT := EC_DEVICE_STATE_INIT, PREOP := EC_DEVICE_STATE_PREOP, BOOTSTRAP := EC_DEVICE_STATE_BOOTSTRAP, SAFEOP := EC_DEVICE_STATE_SAFEOP, OP := EC_DEVICE_STATE_OP ) WORD := INVALID; END_TYPE Related: * `E_DeviceState`_ ST_DeviceDiagnostic ^^^^^^^^^^^^^^^^^^^ :: // PLC Terminal device diagnostics TYPE ST_DeviceDiagnostic : STRUCT {attribute 'pytmc' := ' pv: TYPE io: i '} sType : STRING[15]; {attribute 'pytmc' := ' pv: ADDRESS io: i '} nAddr : WORD; {attribute 'pytmc' := ' pv: NAME io: i '} sName : STRING[31]; {attribute 'pytmc' := ' pv: DEVICE_STATE io: i field: ZRVL 0 field: ONVL 1 field: TWVL 2 field: THVL 3 field: FRVL 4 field: SXVL 8 field: ZRST INVALID field: ONST INIT field: TWST PREOP field: THST BOOTSTRAP field: FRST SAFEOP field: SXST OP field: ZRSV MAJOR field: ONSV MAJOR field: TWSV MAJOR field: THSV MAJOR field: FRSV MAJOR field: FVSV MINOR field: SXSV NO_ALARM field: UNSV MAJOR '} nDeviceState : ENUM; {attribute 'pytmc' := ' pv: DEVICE_STATE_FLAGS io: i '} nDeviceStateFlags : BYTE; {attribute 'pytmc' := ' pv: LINK_STATE_FLAGS io: i '} nLinkStateFlags : BYTE; END_STRUCT END_TYPE ST_ErrorStates ^^^^^^^^^^^^^^ :: // Error states that can occur in the program TYPE ST_ErrorStates : STRUCT // Leak Alarms bLeakDetected : BOOL; // Leak detected on one of the sensors // Hardware Failure bHardwareFailure : BOOL; // A hardware component has failed END_STRUCT END_TYPE GVLs ---- GVL_IO ^^^^^^ :: // PLC IO from terminal modules {attribute 'qualified_only'} VAR_GLOBAL (* Slot1 EL2212 -- Cooling Valve Outputs *) {attribute 'pytmc' := ' pv: @(PREFIX):VALVE:01 io: i field: ZNAM "CLOSED" field: ONAM "OPEN" '} {attribute 'TcLinkTo' := 'TIIB[FTL_PLC_E02_EL2212]^DOX Control Channel 1^Control^Output'} bChillerValve01 AT %Q* : BOOL; // Ch1 {attribute 'pytmc' := ' pv: @(PREFIX):VALVE:02 io: i field: ZNAM "CLOSED" field: ONAM "OPEN" '} {attribute 'TcLinkTo' := 'TIIB[FTL_PLC_E02_EL2212]^DOX Control Channel 2^Control^Output'} bChillerValve02 AT %Q* : BOOL; // Ch2 (* Slot2 EL1004 -- Leak Detector Inputs *) {attribute 'pytmc' := ' pv: @(PREFIX):DETECTOR:01 io: i field: ZNAM "OK" field: ONAM "LEAK" '} {attribute 'TcLinkTo' := 'TIIB[FTL_PLC_E03_EL1004]^Channel 1^Input'} bLeakRelay01 AT %I* : BOOL; // Ch1 {attribute 'pytmc' := ' pv: @(PREFIX):DETECTOR:02 io: i field: ZNAM "OK" field: ONAM "LEAK" '} {attribute 'TcLinkTo' := 'TIIB[FTL_PLC_E03_EL1004]^Channel 2^Input'} bLeakRelay02 AT %I* : BOOL; // Ch2 END_VAR GVL_PLC ^^^^^^^ :: // PLC global variables {attribute 'qualified_only'} VAR_GLOBAL AMSNetId AT %I* : AMSNETID; END_VAR POUs ---- F_ChillerValveEnable ^^^^^^^^^^^^^^^^^^^^ :: // Interlock logic for the Dump Chiller FUNCTION F_ChillerValveEnable : BOOL VAR_INPUT END_VAR VAR_IN_OUT CONSTANT stErrors : ST_ErrorStates; // Currently detected errors END_VAR VAR END_VAR IF stErrors.bHardwareFailure OR stErrors.bLeakDetected THEN F_ChillerValveEnable := FALSE; ELSE F_ChillerValveEnable := TRUE; END_IF END_FUNCTION Related: * `ST_ErrorStates`_ FB_EcatDiag ^^^^^^^^^^^ :: (* Ecat bus diagnostic tool 2015-11-4 Alex Wallace This function block checks the states of all slaves on the ecat bus network, it could be modified to export the states of the slaves on an individual basis, but for now it sets the output boolean true if all slaves are OP and false otherwise. To start the block provide a falling edge on the first pass boolean input. 2018-05-05 Margaret Ghaly Function block has been modified to retrieve the Device State of the Ethercat Master. It also exports the states and information of each individual configured Slave. And saves them in the array q_aEcConfSlaveInfo. 2019-08-28 Nathan Telles Customized for EPS project using PyTMC. Export all states to a structure which is linked to EPICS records. Remove unecessary diagnostics. *) {attribute 'call_after_init'} FUNCTION_BLOCK FB_EcatDiag VAR_INPUT {attribute 'naming' := 'omit'} AMSNetId : AMSNETID; //Link to the AMSNETID name in the ethercat master info. //i_xFirstPass: BOOL; //Hook to system first pass boolean for proper intialization (must be true for the first cycle of the PLC) END_VAR VAR_OUTPUT {attribute 'pytmc' := ' pv: ERROR io: i field: OSV MAJOR field: ZSV NO_ALARM field: ZNAM OK field: ONAM ERROR '} bError : BOOL; {attribute 'pytmc' := ' pv: SLAVES_OK io: i field: OSV NO_ALARM field: ZSV MAJOR field: ZNAM ERROR field: ONAM OK '} bAllSlaveStatesGood: BOOL; // Set to True if all Slaves are in OP State {attribute 'pytmc' := ' pv: MASTER_OK io: i field: OSV NO_ALARM field: ZSV MAJOR field: ZNAM ERROR field: ONAM OK '} bMasterStateGood : BOOL; END_VAR VAR {attribute 'pytmc' := ' pv: MASTER_DEV_STATE io: i '} nMasterDevState: WORD; // The Device State of the Master {attribute 'pytmc' := ' pv: MASTER_STATE io: i '} nMasterState: WORD; // The State of the Master {attribute 'pytmc' := ' pv: AMS_ID io: i '} sNetId: STRING(23); //NetId string astTermStates: ARRAY[1..MAX_SLAVES] OF ST_EcSlaveState; //ECAT Slave States Buffer astEcConfSlaveInfo: ARRAY[1..MAX_SLAVES] OF ST_EcSlaveConfigData; //ECAT Slave Configs Buffer fbGetAllSlaveStates: FB_EcGetAllSlaveStates; //Acquires the ECAT Slave States puts them into astTermStates fbGetMasterDevState: FB_EcGetMasterDevState; //Acquires ECAT Master State fbGetMasterState: FB_EcGetMasterState; //Acquires ECAT Master State fbGetConfSlaves: FB_EcGetConfSlaves; //Acquires the ECAT slave configuration of the bus (how many, what kind, etc) {attribute 'naming' := 'omit'} ftReset: F_TRIG; //Reset trigger sensor {attribute 'naming' := 'omit'} ftMasterDevReset: F_TRIG; //Retrigger sensor for GetMasterDevState {attribute 'naming' := 'omit'} ftMasterReset: F_TRIG; //Retrigger sensor for GetMasterState {attribute 'pytmc' := 'pv: SLAVE01'} stSlave01 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE02'} stSlave02 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE03'} stSlave03 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE04'} stSlave04 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE05'} stSlave05 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE06'} stSlave06 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE07'} stSlave07 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE08'} stSlave08 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE09'} stSlave09 : ST_DeviceDiagnostic; {attribute 'pytmc' := 'pv: SLAVE10'} stSlave10 : ST_DeviceDiagnostic; aSlaves : ARRAY [1..MAX_SLAVES] OF POINTER TO ST_DeviceDiagnostic := [ADR(stSlave01),ADR(stSlave02), ADR(stSlave03),ADR(stSlave04),ADR(stSlave05),ADR(stSlave06),ADR(stSlave07),ADR(stSlave08), ADR(stSlave09), ADR(stSlave10)]; nIterator: UINT; bInit : BOOL := TRUE; {attribute 'pytmc' := ' pv: SLAVES_COUNT io: i '} nSlaves : UINT; END_VAR VAR CONSTANT MAX_SLAVES : UINT := 10; END_VAR sNetId := F_CreateAmsNetId(AMSNetId); //Query the state of all terminals, collect in astTermStates ftReset(CLK:=fbGetAllSlaveStates.bBusy OR bInit); fbGetAllSlaveStates.bExecute := ftReset.Q; fbGetAllSlaveStates(sNetId:=sNetId, pStateBuf := ADR(astTermStates), cbBufLen:=SIZEOF(astTermStates)); //Keep checking... //Cycle through each entry in the array and check if we have anyone not in OP and that the link state is good. // If so, then set our global IO bad boolean. IF fbGetAllSlaveStates.nSlaves > 0 THEN bAllSlaveStatesGood := TRUE; FOR nIterator := 1 TO MAX_SLAVES DO IF nIterator > fbGetAllSlaveStates.nSlaves // Slave is missing OR NOT( (astTermStates[nIterator].deviceState = EC_DEVICE_STATE_OP) AND (astTermStates[nIterator].linkState = EC_LINK_STATE_OK)) THEN bAllSlaveStatesGood := FALSE; END_IF IF nIterator <= fbGetAllSlaveStates.nSlaves THEN aSlaves[nIterator]^.nDeviceState := astTermStates[nIterator].deviceState AND EC_DEVICE_STATE_MASK; aSlaves[nIterator]^.nDeviceStateFlags := SHR(astTermStates[nIterator].deviceState, 4); aSlaves[nIterator]^.nLinkStateFlags :=astTermStates[nIterator].linkState; ELSE // Missing slaves should be set to invalid aSlaves[nIterator]^.nDeviceState := E_DeviceState.INVALID; END_IF END_FOR END_IF // Read the EtherCAT state of the master. If the call is successful, //the State output variable of type WORD contains the requested status information. ftMasterDevReset(CLK:=fbGetMasterDevState.bBusy OR bInit); fbGetMasterDevState(sNetId:= sNetId, bExecute:=ftMasterDevReset.Q, nDevState => nMasterDevState); ftMasterReset(CLK:=fbGetMasterState.bBusy OR bInit); fbGetMasterState(sNetId:= sNetId, bExecute:=ftMasterDevReset.Q, state => nMasterState); bMasterStateGood:= (fbGetMasterState.state = BYTE_TO_UINT(EC_DEVICE_STATE_OP) AND fbGetMasterDevState.nDevState = 0); //This function is used to read a list of all configured slaves from the EtherCat master object Directory //needs to run only once fbGetConfSlaves(bExecute := bInit, sNetId :=sNetId, pArrEcConfSlaveInfo := ADR(astEcConfSlaveInfo),cbBufLen := SIZEOF(astEcConfSlaveInfo)); nSlaves:=fbGetConfSlaves.nSlaves; IF NOT (fbGetConfSlaves.bBusy) THEN FOR nIterator := 1 TO MIN(MAX_SLAVES, fbGetConfSlaves.nSlaves) DO IF nIterator > fbGetConfSlaves.nSlaves THEN // Missing terminal aSlaves[nIterator]^.nAddr := 0; aSlaves[nIterator]^.sName := ''; aSlaves[nIterator]^.sType := ''; ELSE aSlaves[nIterator]^.nAddr :=astEcConfSlaveInfo[nIterator].nAddr; aSlaves[nIterator]^.sName :=astEcConfSlaveInfo[nIterator].sName; aSlaves[nIterator]^.sType :=astEcConfSlaveInfo[nIterator].sType; END_IF END_FOR fbGetConfSlaves.bExecute := FALSE; END_IF bInit := FALSE; bError := fbGetMasterDevState.bError OR fbGetMasterState.bError OR fbGetAllSlaveStates.bError OR fbGetConfSlaves.bError OR NOT bAllSlaveStatesGood OR NOT bMasterStateGood; END_FUNCTION_BLOCK Related: * `E_DeviceState`_ * `ST_DeviceDiagnostic`_ FB_ErrorTriggers ^^^^^^^^^^^^^^^^ :: // Detects rising-edge errors FUNCTION_BLOCK FB_ErrorTriggers VAR_IN_OUT CONSTANT stErrors : ST_ErrorStates; // Errors that have been detected END_VAR VAR_OUTPUT stNewErrors : ST_ErrorStates; // Rising-edge errors END_VAR VAR // R_TRIG function blocks for detecting new errors fbLeakTrig : R_TRIG; END_VAR fbLeakTrig( CLK := stErrors.bLeakDetected, Q => stNewErrors.bLeakDetected ); END_FUNCTION_BLOCK // Returns whether a rising-edge leak error has occured METHOD NewLeakError : BOOL VAR_INPUT END_VAR NewLeakError := fbLeakTrig.Q; END_METHOD // Reset latched leak errors METHOD ResetLeakTriggers VAR_INPUT END_VAR fbLeakTrig( CLK := FALSE, Q => stNewErrors.bLeakDetected ); END_METHOD Related: * `ST_ErrorStates`_ FB_LeakMonitor ^^^^^^^^^^^^^^ :: // Detect leaks and leak locations FUNCTION_BLOCK FB_LeakMonitor VAR_INPUT bLeakRelay01 : BOOL; // Whether a leak was detected on sensor 1 bLeakRelay02 : BOOL; // Whether a leak was detected on sensor 2 END_VAR VAR_OUTPUT {attribute 'pytmc' := ' pv: LEAK_FOUND io: i field: OSV MAJOR field: ZSV NO_ALARM field: ZNAM OK field: ONAM LEAK '} bError: BOOL := FALSE; //Error flag END_VAR (* Set error flag if leak is detected on either sensor*) IF (bLeakRelay01 = FALSE OR bLeakRelay02 = FALSE) THEN bError := TRUE; END_IF END_FUNCTION_BLOCK // Reset latched errors METHOD Reset VAR_INPUT END_VAR bError := FALSE; END_METHOD MAIN ^^^^ :: PROGRAM MAIN VAR stErrors : ST_ErrorStates; // Triggered error states fbErrorTriggers : FB_ErrorTriggers; // Brand new errors this cycl {attribute 'pytmc' := 'pv: @(PREFIX):LEAK_MONITOR'} fbLeakMonitor : FB_LeakMonitor; //Leak detection {attribute 'pytmc' := ' pv: @(PREFIX):VALVE:RESET io: o '} bValveLatchReset : BOOL; //Manual latch for valves (* {attribute 'pytmc' := 'pv: @(PREFIX):FTL:PLC'} fbDiag : FB_EcatDiag; // Ethercat diagnostic *) END_VAR (* (* Check PLC hardware for failures *) fbDiag(AMSNetId := GVL_PLC.AMSNetId); stErrors.bHardwareFailure := fbDiag.bError; *) (* Monitor for leaks on cooling loop*) fbLeakMonitor( bLeakRelay01 := GVL_IO.bLeakRelay01, bLeakRelay02 := GVL_IO.bLeakRelay02, bError => stErrors.bLeakDetected ); (* Open NC chiller loop valves if interlock logic passes *) GVL_IO.bChillerValve01 := F_ChillerValveEnable(stErrors := stErrors); GVL_IO.bChillerValve02 := F_ChillerValveEnable(stErrors := stErrors); (* Check for rising-edge errors *) fbErrorTriggers(stErrors := stErrors); (* Check for new leak errors *) IF fbErrorTriggers.NewLeakError() THEN bValveLatchReset := FALSE; //If there is a new error turn off the valve reset ELSIF bValveLatchReset THEN // If there are no new errors and reset is enabled, reset the errors fbLeakMonitor.Reset(); fbErrorTriggers.ResetLeakTriggers(); END_IF END_PROGRAM Related: * `FB_EcatDiag`_ * `FB_ErrorTriggers`_ * `FB_LeakMonitor`_ * `F_ChillerValveEnable`_ * `GVL_IO`_ * `GVL_PLC`_ * `ST_ErrorStates`_