765-4501 port configuration query

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;

Hi Justin,

Here is a test project I made that an read/write the IOL port configurations. Hope this helps!

Example - Runtime Port Configuration.project (980 KB)