I have a PFC200 (750-8217, FW 04.07.03(29)) connected to a 765-4501 IO-Link master, programmed in Structural Text using CODESYS 3.5 (SP21, Patch 1). The set-up is designed to accommodate various hardware configurations, and I would like to query whether certain ports on the IO-Link master are configured as DO, DI, or IOL. I have tried to use EtherNetIP Services (ENIP) library (v. 4.5.0.0) to conduct an async query for this. The code I have is provided below, but this returns the error: “PATH_DESTINATION_UNKNOWN”. Grateful for any advice to help resolve this issue.
FUNCTION_BLOCK PUBLIC FbIOLMaster_PortCheck
(*
Authors : JV
Last Edit : 03 Sep 2026 (JV)
Device : PLC
FB to manage async-communications with IO-Link master to check port configuration
(Note: if want to async set config, could adapt this code to use the Set_Attribute_Single FB)
EXECUTION FB - EDGE TRIGGERED
*)
VAR_IN_OUT
xTrigger : BOOL; // Starts with rising edge on Trigger
END_VAR
VAR_INPUT
xNewMale : BOOL; // Trigger value of boom
eNewIOLMaster : EnIOLMaster; // Trigger value of IOLink master type
usiNewMaster : USINT; // Trigger value of IOLink master index
usiNewPort : USINT; // Trigger value of IO-Link master physical port number (e.g. 1-8)
END_VAR
VAR_OUTPUT
oStatus : StFbEdgeStatus; // structure reporting fb status
END_VAR
VAR
// EIP library variables
oEIP : ENIP.Get_Attribute_Single;
xEIPTrigger : BOOL;
xEIPDone : BOOL;
xEIPBusy : BOOL;
xEIPError : BOOL;
eEIPError : ENIP.ERROR;
itfEIPDevice : ENIP.IEtherNetIPService;
oEIPClass : ENIP.CIPClass := 16#F4; // Port Object (possibly 16#0381, 16#F4)
dwEIPInstance : DWORD;
wEIPAttribute : WORD;
byPortMode : BYTE;
udiEIPReadDataSize : UDINT;
// local variables
xInit : BOOL;
eState : EnIOLMasterComms; // current state of machine
eIOLMaster : EnIOLMaster; // type of IOLink master
usiMaster : USINT; // IOLink master index
xMale : BOOL; // boom
usiPort : USINT; // IO-Link master physical port number (e.g. 1-8)
tTimeout : TIME := T#30S; // timeout
oWatchDog : TON;
oLogEntry : StLogEntry;
END_VAR
IF NOT xTrigger THEN
(* ========================*)
(* ===== [ Set idle ] =====*)
(* ========================*)
eState := EnIOLMasterComms.Idle;
ELSE
(* ===============================*)
(* ===== [ Process trigger ] =====*)
(* ===============================*)
oStatus.xDone := FALSE;
oStatus.xError := FALSE;
IF oStatus.xBusy THEN
// already busy - check if need to abort current task
IF xNewMale <> xMale OR
eNewIOLMaster <> eIOLMaster OR
usiNewMaster <> usiMaster OR
usiNewPort <> usiPort THEN
// abort existing task
xInit := TRUE;
eState := EnIOLMasterComms.CheckEIPConnection;
oStatus.sMessage := 'New task started - previous task aborted';
END_IF
ELSE
xInit := TRUE;
eState := EnIOLMasterComms.CheckEIPConnection;
oStatus.sMessage := 'New task started';
END_IF
IF xInit THEN
xMale := xNewMale;
eIOLMaster := eNewIOLMaster;
usiMaster := usiNewMaster;
usiPort := usiNewPort;
oStatus.xBusy := TRUE;
IF xMale AND eIOLMaster = EnIOLMaster.MURR THEN
itfEIPDevice := IOLMMURR1;
ELSIF xMale AND eIOLMaster = EnIOLMaster.WAGO AND usiMaster = 1 THEN
itfEIPDevice := IOLMWAGO1;
ELSIF xMale AND eIOLMaster = EnIOLMaster.WAGO AND usiMaster = 2 THEN
itfEIPDevice := IOLMWAGO2;
ELSIF NOT xMale AND eIOLMaster = EnIOLMaster.MURR THEN
itfEIPDevice := IOLFMURR1;
ELSIF NOT xMale AND eIOLMaster = EnIOLMaster.WAGO AND usiMaster = 1 THEN
itfEIPDevice := IOLFWAGO1;
ELSIF NOT xMale AND eIOLMaster = EnIOLMaster.WAGO AND usiMaster = 2 THEN
itfEIPDevice := IOLFWAGO2;
ELSE
oStatus.xBusy := FALSE;
oStatus.xDone := FALSE;
oStatus.xError := TRUE;
oStatus.sMessage := 'Unrecognised system for IOLMaster_Comms';
eState := EnIOLMasterComms.Waiting;
END_IF
xInit := FALSE;
END_IF
END_IF
(* =============================*)
(* ===== [ State Machine ] =====*)
(* =============================*)
CASE eState OF
EnIOLMasterComms.Idle:
IF oStatus.xBusy THEN
oStatus.sMessage := 'Idle - previous task aborted';
ELSE
oStatus.sMessage := 'Idle';
END_IF
oStatus.xBusy := FALSE;
oStatus.xDone := FALSE;
oStatus.xError := FALSE;
EnIOLMasterComms.CheckEIPConnection :
IF NOT FnIOL_CheckAdapterRunning(eIOLMaster:=eIOLMaster, usiMaster:=usiMaster, xMale:=xMale) THEN
oStatus.xBusy := FALSE;
oStatus.xDone := TRUE;
oStatus.sMessage := 'IOL master not running';
ELSE
eState := EnIOLMasterComms.InitialiseRead;
END_IF
EnIOLMasterComms.InitialiseRead :
xEIPTrigger := TRUE; // Setting this variable TRUE will execute the EIP function block
dwEIPInstance := USINT_TO_DWORD(usiPort); // This is the physical port number (1-8)
wEIPAttribute := 1; // 1 = Pin 4, 2 = Pin 2
eState := EnIOLMasterComms.Waiting;
EnIOLMasterComms.Waiting :
;
END_CASE
(* ===============================*)
(* ===== [ Pass to workers ] =====*)
(* ===============================*)
oEIP(
xExecute := xEIPTrigger,
xDone => xEIPDone,
xBusy => xEIPBusy,
xError => xEIPError,
eError => eEIPError,
itfEtherNetIPDevice := itfEIPDevice,
eClass := oEIPClass,
dwInstance := dwEIPInstance,
wAttribute := wEIPAttribute,
pData := ADR(byPortMode),
udiDataSize := SIZEOF(byPortMode),
udiReceivedDataSize => udiEIPReadDataSize
);
IF xEIPError THEN
xEIPTrigger := FALSE;
oStatus.xBusy := FALSE;
oStatus.xError := TRUE;
oStatus.sMessage := WagoAppString.Concat3('EIP error: ','', TO_STRING(eEIPError));
ELSIF xEIPDone THEN
xEIPTrigger := FALSE;
oStatus.xBusy := FALSE;
oStatus.xDone := TRUE;
oStatus.sMessage := 'Complete';
END_IF
(* ==============================*)
(* ===== [ Watchdog Timer ] =====*)
(* ==============================*)
IF oWatchDog.Q AND oStatus.xBusy THEN
oStatus.xBusy := FALSE;
oStatus.xError := TRUE;
oStatus.sMessage := 'Timeout';
END_IF
oWatchDog( IN := oStatus.xBusy, PT := tTimeout);
(* =============================*)
(* ===== [ Log reporting ] =====*)
(* =============================*)
IF oStatus.xError OR oStatus.xDone THEN
IF oStatus.xError THEN
oLogEntry.udiLogClass := CmpLog.LogClass.LOG_ERROR;
ELSE
oLogEntry.udiLogClass := CmpLog.LogClass.LOG_INFO;
END_IF
IF xMale THEN
oLogEntry.enCallingUnit := EnPOUReference.MaleFbIOLMaster_Comms;
ELSE
oLogEntry.enCallingUnit := EnPOUReference.FemaleFbIOLMaster_Comms;
END_IF
oLogEntry.sMessage := oStatus.sMessage;
GvSQL.oSQLProcessing.AddLogEntry(oLogEntry);
END_IF
(* ===============================*)
(* ===== [ Update xTrigger ] =====*)
(* ===============================*)
xTrigger := oStatus.xBusy;