520 lines
16 KiB
XML
520 lines
16 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<TcPlcObject Version="1.1.0.1">
|
|
<POU Name="FB_Valve" Id="{658f8bac-5e87-43a2-9b4c-a425a60acda2}" SpecialFunc="None">
|
|
<Declaration><![CDATA[FUNCTION_BLOCK FINAL FB_Valve
|
|
VAR_INPUT
|
|
xOpenFeedback AT %I* : BOOL;
|
|
xCloseFeedback AT %I* : BOOL;
|
|
|
|
// Open and close the valve
|
|
xAutomaticOpen : BOOL;
|
|
|
|
// Global switch to dissable all errors
|
|
xReleaseErrors : BOOL := TRUE;
|
|
|
|
// Config input
|
|
// Valve configuration parameters
|
|
stValveConfig : ST_ValveConfig;
|
|
|
|
// Release or block change to manual mode
|
|
xReleaseManualMode : BOOL;
|
|
|
|
// Process interlocks
|
|
wProcessINTLK : T_INTERLOCK;
|
|
wProcessINTLKUsed: T_INTERLOCK;
|
|
|
|
// Safety interlocks
|
|
wSafetyINTLK : T_INTERLOCK;
|
|
|
|
// Used safety interlocks
|
|
wSafetyINTLKUsed: T_INTERLOCK;
|
|
|
|
// Input to confirm all errors
|
|
xConfirmAlarms : BOOL;
|
|
|
|
// Input to tell the fb thats ist used inside a unit test
|
|
// FB will not throw error messages
|
|
{attribute 'hide'}
|
|
xInUnitTestMode : BOOL := FALSE;
|
|
END_VAR
|
|
VAR_OUTPUT
|
|
// Use xOpenValve for normally closed valves
|
|
xOpenValve AT %Q* : BOOL := FALSE;
|
|
|
|
// Use xCloseValve for normally open valves
|
|
xCloseValve AT %Q* : BOOL := TRUE;
|
|
|
|
// Error in valve active
|
|
xError : BOOL;
|
|
END_VAR
|
|
VAR_IN_OUT
|
|
// HMI interface
|
|
stHMIInterface : ST_HMI_VALVE_DATA;
|
|
END_VAR
|
|
VAR
|
|
// Internal command for manual mode open request
|
|
_xManualOpen : BOOL := FALSE;
|
|
|
|
// Manual mode active
|
|
_xManualModeActive : BOOL := FALSE;
|
|
|
|
// Automatic mode active
|
|
_xAutomaticModeActive : BOOL := TRUE;
|
|
|
|
// Sum of all activated interlocks
|
|
_xProcessINTLKOk : BOOL;
|
|
|
|
// Sum of all activated process interlocks
|
|
_xSafetyINTLKOk : BOOL := TRUE;
|
|
|
|
// Internal open state of the valve
|
|
_xIsOpen : BOOL;
|
|
|
|
// Internal closed state of the valve
|
|
_xIsClosed : BOOL;
|
|
|
|
// Name of valve
|
|
// Will be set in constructor and can be changed with the name property
|
|
_sName : STRING;
|
|
|
|
// Alarm handler for valve did not open
|
|
_fbAlarmDidNotOpen : FB_AlarmMessage(stEventEntry := TC_EVENTS.Valve.DidNotOpen, xWithConfirmation := TRUE);
|
|
|
|
// Alarm handler for valve did not close
|
|
_fbAlarmDidNotClose : FB_AlarmMessage(stEventEntry := TC_EVENTS.Valve.DidNotClose, xWithConfirmation := TRUE);
|
|
|
|
// Internal open command
|
|
_xOpenValve : BOOL;
|
|
|
|
// Helper variables used in reset error flag
|
|
_xAlarmsActive : BOOL;
|
|
_xInputErrorsActive : BOOL;
|
|
|
|
// Internal error flags
|
|
_xError : BOOL;
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[// ===============
|
|
// Handle not used
|
|
// ===============
|
|
|
|
IF (NOT stValveConfig.xUsed) THEN
|
|
// Clear all pending alarms if there are any
|
|
_fbAlarmDidNotOpen(xActive := FALSE, xRelease := FALSE);
|
|
_fbAlarmDidNotClose(xActive := FALSE, xRelease := FALSE);
|
|
|
|
_xError := FALSE;
|
|
xError := FALSE;
|
|
|
|
RETURN;
|
|
END_IF
|
|
|
|
|
|
// ===========================
|
|
// Reset safetyinterlocks flag
|
|
// ===========================
|
|
|
|
IF xConfirmAlarms AND (NOT _xSafetyINTLKOk) THEN
|
|
_xSafetyINTLKOk := TRUE;
|
|
END_IF
|
|
|
|
|
|
// =========
|
|
// Prechecks
|
|
// =========
|
|
|
|
CheckInterlocks();
|
|
|
|
|
|
// =================
|
|
// Handle HMI inputs
|
|
// =================
|
|
|
|
HandleHMIInput();
|
|
|
|
|
|
// ===================================
|
|
// Handle opening and closing of valve
|
|
// ===================================
|
|
|
|
_xOpenValve := ((_xManualOpen AND _xManualModeActive AND (NOT _xAutomaticModeActive))
|
|
OR ( xAutomaticOpen AND _xAutomaticModeActive AND (NOT _xManualModeActive)));
|
|
|
|
// Check for interlocks
|
|
// If not, use valve state from settings
|
|
IF (NOT _xProcessINTLKOk) OR (NOT _xSafetyINTLKOk) THEN
|
|
_xOpenValve := stValveConfig.xOpenWhenInterlocksActive;
|
|
|
|
// Also reset manual open command if safetyinterlocks are set
|
|
IF (NOT _xSafetyINTLKOk) AND (_xManualOpen <> stValveConfig.xOpenWhenInterlocksActive) THEN
|
|
_xManualOpen := stValveConfig.xOpenWhenInterlocksActive;
|
|
END_IF
|
|
END_IF
|
|
|
|
// Handle open close feedback
|
|
_xIsOpen := (stValveConfig.xHasOpenFeedback AND xOpenFeedback AND (NOT xCloseFeedback)) OR ((NOT stValveConfig.xHasOpenFeedback) AND _xOpenValve);
|
|
_xIsClosed := (stValveConfig.xHasClosedFeedback AND xCloseFeedback AND (NOT xOpenFeedback)) OR ((NOT stValveConfig.xHasClosedFeedback) AND (NOT _xOpenValve));
|
|
|
|
|
|
// ===========================
|
|
// Valve did not open in time
|
|
// ===========================
|
|
|
|
_fbAlarmDidNotOpen(
|
|
xActive:= _xOpenValve AND (NOT _xIsOpen),
|
|
xRelease:= xReleaseErrors,
|
|
xAcknowledge:= xConfirmAlarms,
|
|
timOnDelay:= stValveConfig.timTimeoutOpen,
|
|
timOffDelay:= T#0S,
|
|
xInUnitTestMode:= xInUnitTestMode);
|
|
|
|
// Latch error signal
|
|
IF _fbAlarmDidNotOpen.Triggered THEN
|
|
_xError := TRUE;
|
|
END_IF
|
|
|
|
|
|
// ===========================
|
|
// Valve did not close in time
|
|
// ===========================
|
|
|
|
_fbAlarmDidNotClose(
|
|
xActive:= (NOT _xOpenValve) AND (NOT _xIsClosed),
|
|
xRelease:= xReleaseErrors,
|
|
xAcknowledge:= xConfirmAlarms,
|
|
timOnDelay:= stValveConfig.timTimeoutClose,
|
|
timOffDelay:= T#0S,
|
|
xInUnitTestMode:= xInUnitTestMode);
|
|
|
|
// Latch error signal
|
|
IF _fbAlarmDidNotClose.Triggered THEN
|
|
_xError := TRUE;
|
|
END_IF
|
|
|
|
|
|
// ================
|
|
// Reset error flag
|
|
// ================
|
|
|
|
_xAlarmsActive := _fbAlarmDidNotOpen.Active OR _fbAlarmDidNotClose.Active;
|
|
_xInputErrorsActive := _fbAlarmDidNotOpen.Triggered OR _fbAlarmDidNotClose.Triggered;
|
|
|
|
IF xConfirmAlarms AND _xError AND (NOT _xAlarmsActive) AND (NOT _xInputErrorsActive) THEN
|
|
_xError := FALSE;
|
|
END_IF
|
|
|
|
|
|
// ================================
|
|
// Copy internal signals to outputs
|
|
// ================================
|
|
|
|
xOpenValve := _xOpenValve;
|
|
xCloseValve := (NOT _xOpenValve);
|
|
|
|
xError := _xError;
|
|
|
|
|
|
// ==================
|
|
// Handle HMI outputs
|
|
// ==================
|
|
|
|
HandleHMIOutput();]]></ST>
|
|
</Implementation>
|
|
<Method Name="CheckInterlocks" Id="{30a74d3e-2b2c-4675-9a57-4f19eacc7578}">
|
|
<Declaration><![CDATA[METHOD PRIVATE FINAL CheckInterlocks
|
|
VAR
|
|
END_VAR]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[// Set interlocks ok
|
|
_xProcessINTLKOk := TRUE;
|
|
|
|
// Check if all active interlocks are ok
|
|
IF ((wProcessINTLK AND wProcessINTLKUsed) XOR wProcessINTLKUsed) > 0 THEN
|
|
_xProcessINTLKOk := FALSE;
|
|
END_IF
|
|
|
|
// Check safety interlocks
|
|
// Safety interlocks will not automatically reset
|
|
// They need to be reset via the xConfirmAlarms input
|
|
IF ((wSafetyINTLK AND wSafetyINTLKUsed) XOR wSafetyINTLKUsed) > 0 THEN
|
|
_xSafetyINTLKOk := FALSE;
|
|
END_IF]]></ST>
|
|
</Implementation>
|
|
</Method>
|
|
<Method Name="CreateAlarmMSG" Id="{afc73f4f-5060-43b3-9def-13568c293122}">
|
|
<Declaration><![CDATA[METHOD PRIVATE CreateAlarmMSG
|
|
VAR_INPUT
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[// Create did not in open alarm
|
|
_fbAlarmDidNotOpen.Arguments.Clear().AddString(_sName);
|
|
|
|
// Create did not close alarm
|
|
_fbAlarmDidNotClose.Arguments.Clear().AddString(_sName);]]></ST>
|
|
</Implementation>
|
|
</Method>
|
|
<Method Name="FB_init" Id="{0feef5ff-7efd-4523-9e63-83898cab15b4}">
|
|
<Declaration><![CDATA[//FB_Init ist immer implizit verfügbar und wird primär für die Initialisierung verwendet.
|
|
//Der Rückgabewert wird nicht ausgewertet. Für gezielte Einflussnahme können Sie
|
|
//die Methoden explizit deklarieren und darin mit dem Standard-Initialisierungscode
|
|
//zusätzlichen Code bereitstellen. Sie können den Rückgabewert auswerten.
|
|
METHOD FB_Init: BOOL
|
|
VAR_INPUT
|
|
bInitRetains: BOOL; // TRUE: Die Retain-Variablen werden initialisiert (Reset warm / Reset kalt)
|
|
bInCopyCode: BOOL; // TRUE: Die Instanz wird danach in den Kopiercode kopiert (Online-Change)
|
|
|
|
sName : STRING(80);
|
|
END_VAR]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[_sName := sName;
|
|
|
|
// Create alarm messages
|
|
CreateAlarmMSG();]]></ST>
|
|
</Implementation>
|
|
</Method>
|
|
<Method Name="HandleHMIInput" Id="{daeab2cd-6c5b-4055-97d1-f9300c25c2a5}">
|
|
<Declaration><![CDATA[METHOD PRIVATE HandleHMIInput
|
|
VAR_INPUT
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[{warning disable C0371}
|
|
// Handle automatic mode request
|
|
IF stHMIInterface.stAutomaticButton.xRequest AND (NOT _xAutomaticModeActive) THEN
|
|
_xAutomaticModeActive := TRUE;
|
|
_xManualModeActive := FALSE;
|
|
END_IF
|
|
|
|
IF stHMIInterface.stAutomaticButton.xRequest THEN
|
|
stHMIInterface.stAutomaticButton.xRequest := FALSE;
|
|
END_IF
|
|
|
|
// Handle manual mode request
|
|
IF stHMIInterface.stManualButton.xRequest AND (NOT _xManualModeActive) AND xReleaseManualMode THEN
|
|
_xAutomaticModeActive := FALSE;
|
|
_xManualModeActive := TRUE;
|
|
|
|
// Copy last requested valve state into manual
|
|
_xManualOpen := xAutomaticOpen;
|
|
END_IF
|
|
|
|
IF stHMIInterface.stManualButton.xRequest THEN
|
|
stHMIInterface.stManualButton.xRequest := FALSE;
|
|
END_IF
|
|
|
|
// Handle open request
|
|
IF stHMIInterface.stOpenButton.xRequest AND _xManualModeActive AND _xProcessINTLKOk AND _xSafetyINTLKOk THEN
|
|
_xManualOpen := TRUE;
|
|
END_IF
|
|
|
|
IF stHMIInterface.stOpenButton.xRequest THEN
|
|
stHMIInterface.stOpenButton.xRequest := FALSE;
|
|
END_IF
|
|
|
|
// Handle close request
|
|
IF stHMIInterface.stCloseButton.xRequest AND _xManualModeActive THEN
|
|
_xManualOpen := FALSE;
|
|
END_IF
|
|
|
|
IF stHMIInterface.stCloseButton.xRequest THEN
|
|
stHMIInterface.stCloseButton.xRequest := FALSE;
|
|
END_IF]]></ST>
|
|
</Implementation>
|
|
</Method>
|
|
<Method Name="HandleHMIOutput" Id="{89ada59a-03b1-4917-880c-76f3bba59ef0}">
|
|
<Declaration><![CDATA[METHOD PRIVATE HandleHMIOutput : BOOL
|
|
VAR_INPUT
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[{warning disable C0371}
|
|
// Handle open closed feedbacks
|
|
IF (NOT _xIsOpen) AND (NOT _xIsClosed) AND (NOT _xError) THEN
|
|
stHMIInterface.iStatus := E_HMI_VALVE_STATUS.TRANSITIONING;
|
|
END_IF
|
|
|
|
IF _xIsOpen AND (NOT _xIsClosed) AND (NOT _xError) THEN
|
|
stHMIInterface.iStatus := E_HMI_VALVE_STATUS.OPENED;
|
|
END_IF
|
|
|
|
IF _xIsClosed AND (NOT _xIsOpen) AND (NOT _xError) THEN
|
|
stHMIInterface.iStatus := E_HMI_VALVE_STATUS.CLOSED;
|
|
END_IF
|
|
|
|
IF _xError THEN
|
|
stHMIInterface.iStatus := E_HMI_VALVE_STATUS.ERROR;
|
|
END_IF
|
|
|
|
// Copy config data to hmi interface
|
|
stHMIInterface.xUsed := stValveConfig.xUsed;
|
|
stHMIInterface.sName := _sName;
|
|
|
|
// Handle mode button feedback
|
|
IF _xManualModeActive THEN
|
|
stHMIInterface.stManualButton.eFeedback := E_HMI_BUTTON_FEEDBACK.ACTIVE;
|
|
ELSE
|
|
stHMIInterface.stManualButton.eFeedback := E_HMI_BUTTON_FEEDBACK.NONE;
|
|
END_IF
|
|
|
|
IF _xAutomaticModeActive THEN
|
|
stHMIInterface.stAutomaticButton.eFeedback := E_HMI_BUTTON_FEEDBACK.ACTIVE;
|
|
ELSE
|
|
stHMIInterface.stAutomaticButton.eFeedback := E_HMI_BUTTON_FEEDBACK.NONE;
|
|
END_IF
|
|
|
|
// Handle open close button feedback
|
|
IF _xIsOpen THEN
|
|
stHMIInterface.stOpenButton.eFeedback := E_HMI_BUTTON_FEEDBACK.ACTIVE;
|
|
ELSE
|
|
stHMIInterface.stOpenButton.eFeedback := E_HMI_BUTTON_FEEDBACK.NONE;
|
|
END_IF
|
|
|
|
IF _xIsClosed THEN
|
|
stHMIInterface.stCloseButton.eFeedback := E_HMI_BUTTON_FEEDBACK.ACTIVE;
|
|
ELSE
|
|
stHMIInterface.stCloseButton.eFeedback := E_HMI_BUTTON_FEEDBACK.NONE;
|
|
END_IF
|
|
|
|
// Handle release button signals
|
|
stHMIInterface.stManualButton.xRelease := xReleaseManualMode AND (NOT _xManualModeActive);
|
|
stHMIInterface.stAutomaticButton.xRelease := (NOT _xAutomaticModeActive);
|
|
|
|
stHMIInterface.stOpenButton.xRelease := _xManualModeActive AND _xProcessINTLKOk AND _xSafetyINTLKOk;
|
|
stHMIInterface.stCloseButton.xRelease := _xManualModeActive;
|
|
|
|
// Handle current mode
|
|
IF _xManualModeActive AND (NOT _xAutomaticModeActive) THEN
|
|
stHMIInterface.iCurrentMode := E_HMI_MODE.MANUAL;
|
|
END_IF
|
|
|
|
IF _xAutomaticModeActive AND (NOT _xManualModeActive) THEN
|
|
stHMIInterface.iCurrentMode := E_HMI_MODE.AUTOMATIC;
|
|
END_IF
|
|
|
|
// Handle interlock status
|
|
stHMIInterface.stInterlock.wProcessINTLKStatus := wProcessINTLK;
|
|
stHMIInterface.stInterlock.wProcessINTLKUsed := wProcessINTLKUsed;
|
|
stHMIInterface.stInterlock.xProcessINTLKOk := _xProcessINTLKOk;
|
|
|
|
stHMIInterface.stInterlock.wSafetyINTLKStatus := wSafetyINTLK;
|
|
stHMIInterface.stInterlock.wSafetyINTLKUsed := wSafetyINTLKUsed;
|
|
stHMIInterface.stInterlock.xSafetyINTLKOk := _xSafetyINTLKOk;]]></ST>
|
|
</Implementation>
|
|
</Method>
|
|
<Property Name="IsClosed" Id="{1f4ebe6c-a98f-48b1-8a5f-f4c1debefa5c}">
|
|
<Declaration><![CDATA[PROPERTY IsClosed : BOOL
|
|
]]></Declaration>
|
|
<Get Name="Get" Id="{ef140b66-3db4-4eb7-87ae-680649452cd3}">
|
|
<Declaration><![CDATA[VAR
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[IsClosed := _xIsClosed;]]></ST>
|
|
</Implementation>
|
|
</Get>
|
|
</Property>
|
|
<Property Name="IsInAutomaticMode" Id="{d767111f-e137-4db5-8520-de2e239783bd}">
|
|
<Declaration><![CDATA[PROPERTY IsInAutomaticMode : BOOL
|
|
]]></Declaration>
|
|
<Get Name="Get" Id="{203d88c3-1caf-4d92-8292-06e60d21d256}">
|
|
<Declaration><![CDATA[VAR
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[IsInAutomaticMode := _xAutomaticModeActive AND (NOT _xManualModeActive);
|
|
]]></ST>
|
|
</Implementation>
|
|
</Get>
|
|
</Property>
|
|
<Property Name="IsInManualMode" Id="{ddd53775-56a5-419a-a26e-07865c412725}">
|
|
<Declaration><![CDATA[PROPERTY IsInManualMode : BOOL
|
|
]]></Declaration>
|
|
<Get Name="Get" Id="{93872f82-0dd1-4fc7-bfb1-ae593ff57248}">
|
|
<Declaration><![CDATA[VAR
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[IsInManualMode := _xManualModeActive AND (NOT _xAutomaticModeActive);]]></ST>
|
|
</Implementation>
|
|
</Get>
|
|
</Property>
|
|
<Property Name="IsOpen" Id="{6afdd2c7-3934-4136-a18f-ea14a935f5ef}">
|
|
<Declaration><![CDATA[PROPERTY IsOpen : BOOL
|
|
]]></Declaration>
|
|
<Get Name="Get" Id="{3d3206fd-7dcd-4c1d-a91d-4513ff154aa6}">
|
|
<Declaration><![CDATA[VAR
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[IsOpen := _xIsOpen;]]></ST>
|
|
</Implementation>
|
|
</Get>
|
|
</Property>
|
|
<Property Name="Name" Id="{6475e552-32d7-4dcc-b692-9b5c114c6e55}">
|
|
<Declaration><![CDATA[PROPERTY Name : STRING(80)]]></Declaration>
|
|
<Get Name="Get" Id="{3c945911-435e-4362-80a0-23de08a8aa01}">
|
|
<Declaration><![CDATA[VAR
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[Name := _sName;]]></ST>
|
|
</Implementation>
|
|
</Get>
|
|
<Set Name="Set" Id="{e21a3263-d456-45aa-b72b-78833f59308c}">
|
|
<Declaration><![CDATA[VAR
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[_sName := Name;
|
|
|
|
// Recreate alarm messages after name change
|
|
CreateAlarmMSG();]]></ST>
|
|
</Implementation>
|
|
</Set>
|
|
</Property>
|
|
<Property Name="ProcessInterlocksOK" Id="{5304af5f-5f38-41ac-bae3-41072677ae5b}">
|
|
<Declaration><![CDATA[PROPERTY ProcessInterlocksOK : BOOL
|
|
]]></Declaration>
|
|
<Get Name="Get" Id="{c7a36fe9-16d1-4e6a-840a-95721198ae41}">
|
|
<Declaration><![CDATA[VAR
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[ProcessInterlocksOK := _xProcessINTLKOk;]]></ST>
|
|
</Implementation>
|
|
</Get>
|
|
</Property>
|
|
<Method Name="ReqAutomaticMode" Id="{8d7ea6da-e5b9-4d22-89bf-8b742e336da8}">
|
|
<Declaration><![CDATA[METHOD ReqAutomaticMode
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[_xAutomaticModeActive := TRUE;
|
|
_xManualModeActive := FALSE;]]></ST>
|
|
</Implementation>
|
|
</Method>
|
|
<Method Name="ReqManualMode" Id="{3b374714-1bdd-4b5e-a971-5605e320e287}">
|
|
<Declaration><![CDATA[METHOD ReqManualMode
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[IF xReleaseManualMode THEN
|
|
_xManualModeActive := TRUE;
|
|
_xAutomaticModeActive := FALSE;
|
|
END_IF]]></ST>
|
|
</Implementation>
|
|
</Method>
|
|
<Property Name="SafetyInterlocksOK" Id="{cfa4b65a-d169-4f4b-ad4b-6e2d8e24ce3a}">
|
|
<Declaration><![CDATA[{attribute 'analysis' := '-31'}
|
|
PROPERTY SafetyInterlocksOK : BOOL]]></Declaration>
|
|
<Get Name="Get" Id="{fa7d25b7-22e3-4e23-afcd-0ace84239f31}">
|
|
<Declaration><![CDATA[VAR
|
|
END_VAR
|
|
]]></Declaration>
|
|
<Implementation>
|
|
<ST><![CDATA[SafetyInterlocksOK := _xSafetyINTLKOk;]]></ST>
|
|
</Implementation>
|
|
</Get>
|
|
</Property>
|
|
</POU>
|
|
</TcPlcObject> |