[fb80db]: / CoDeSys_S7Comm.library  Maximize  Restore  History

Download this file

2543 lines (2140 with data), 523.1 kB

-------------------------------------------------------------------------------
Device
-------------------------------------------------------------------------------


FUNCTION_BLOCK Device

VAR_INPUT
    bEnable: BOOL;
    bAutoReconnect: BOOL;
    sIpAddress: string;
    uiPort: UINT;
END_VAR

VAR_OUTPUT
    bTcpActive: BOOL;
    bCommActive: BOOL;
    bBusy: BOOL;
    bReadSuccess: BOOL;
    bError: BOOL;
    sStatus: string;
    sError: string;
    sWarn: string;
END_VAR

VAR
    _bError: BOOL;
    _sError: string;
    _sStatus: string;
    _sHeader: string;
    _tcpClient: NBS.TCP_Client;
    _tcpIpAddr: NBS.IP_ADDR;
    _hTcpConnection: CAA.HANDLE;
    _bEnable: BOOL;
    _bTcpConnected: BOOL;
    _tonTcpWatchDog: TON;
    _udiTcpClientTimeout: UDINT;
    _tTcpClientRetry: TIME;
    _uiRetry: UINT;
    _tcpWrite: NBS.TCP_Write;
    _udiTcpWriteTimeout: UDINT;
    _bTcpWriteEn: BOOL;
    _uiTcpWriteSize: UINT;
    _abTcpWriteBuffer: ARRAY [..] OF ;
    _bTcpWriteData: BOOL;
    _tonTcpWriteWatchDog: TON;
    _tCommWriteTimeout: TIME;
    _tcpRead: NBS.TCP_Read;
    _bRead: BOOL;
    _uiTcpReadSize: UINT;
    _abTcpReadBuffer: ARRAY [..] OF ;
    _sIpAddress: string;
    _uiPort: UINT;
    _bRack: BYTE;
    _bSlot: BYTE;
    _bHexPrefix: BOOL;
    _bDeviceIdentified: BOOL;
    _bSessionRegistered: BOOL;
    _bSetupComm: BOOL;
    _bCommActive: BOOL;
    _bWriteSuccess: BOOL;
    _uiCOTP: UINT;
    _uiROSCTR: UINT;
    _uiFunctionGroup: UINT;
    _uiSubfunction: UINT;
    _uiFunction: UINT;
    _bInitialized: BOOL;
    _uiInitialize: UINT;
    _stConnectReqRes: stConnectReqRes;
    _stSetupCommReq: stSetupCommReq;
    _stSetupCommRes: stSetupCommRes;
    _stDisconnectReq: stDisconnectReq;
    _stCpuInfo: stCpuInfo;
    _bDeviceState: BOOL;
    _uiSZL: UINT;
    _stReadSzlReq: stReadSzlReq;
    _stReadSzlRes: stReadSzlRes;
    _stClockReq: stClockReq;
    _stClockRes: stClockRes;
    _stTimestamp: stTimestamp;
    _timestamp: Timestamp;
    _ltPlcTime: LTIME;
    _ldtPlcTime: LDATE_AND_TIME;
    _stReadDbReq: stReadDbReq;
    _stReadDbRes: stReadDbRes;
    _stWriteDbReq: stWriteDbReq;
    _stWriteDbRes: stWriteDbRes;
    _stParamItem: stParamItem;
    _stCommData: stCommData;
    _uiTemp: UINT;
    _sTemp: string;
END_VAR
// set status
bTcpActive := (_tcpClient.xEnable AND _tcpClient.xActive);
bCommActive := _bCommActive;
bError := _sError <> '';
sError := _sError;
sStatus := _sStatus;

// if disabled, reset and ignore
IF (NOT(bEnable))THEN
	IF (_tcpClient.xEnable) THEN
		THIS^._bResetFB();
	END_IF
	RETURN;
END_IF
IF (_uiPort < 1) THEN
	_sError := 'Check port';
	RETURN;
ELSIF (NOT(bIsIPv4(_sIpAddress))) THEN
	_sError := 'Check IP';
	RETURN;
END_IF

// clear existing error
IF (NOT _bError) THEN
	_sError := '';
END_IF
IF (NOT _bTcpConnected) THEN
	_bError := FALSE;
END_IF

// IP address and port is first set by FB_init
// if you need to change it during runtime
// toggle bEnable to one shot
IF (NOT(_bEnable) AND NOT(_tcpClient.xDone) AND NOT(_tcpClient.xError))THEN
	// change IP during runtime
	IF (bIsIPv4(sIpAddress)) THEN
		_tcpIpAddr.sAddr:= sIpAddress;
	END_IF
	// change port during runtime
	IF (uiPort > 0) THEN
		_uiPort := uiPort;
	END_IF
	_bEnable := TRUE;
END_IF

// =======================================
// Set up client connection
// =======================================
_tcpClient(xEnable:=_bEnable,
			xDone=> ,
			xBusy=> ,
			xError=> ,
			udiTimeOut:=_udiTcpClientTimeout,
			ipAddr:=_tcpIpAddr,
			uiPort:=_uiPort,
			eError=> ,
			xActive=>_bTcpConnected,
			hConnection=>_hTcpConnection);

// on error, retry on specified interval
_tonTcpWatchDog(IN:=(NOT(_tonTcpWatchDog.Q) AND (_tcpClient.xError OR (NOT(_tcpClient.xActive) AND _tcpClient.xDone))), // active if xError, or manually disconnected
				PT:=_tTcpClientRetry,
				Q=> ,
				ET=> );
// re-establish connection if bAutoReconnect is enabled
IF (_tonTcpWatchDog.Q AND bAutoReconnect) THEN
	_bEnable := NOT(_bEnable);
	IF (NOT(_bEnable)) THEN
		THIS^._bResetFB();
	END_IF
	_uiRetry := _uiRetry + 1;
	_bError := _bBuildMessage(sMessage1:='TCP Client Error. Retry #', sMessage2:=UINT_TO_STRING(_uiRetry), pbBuffer:=ADR(_sError));
END_IF
 
// PLC closes connection if idle for ~60 seconds
IF (_tcpClient.xDone) THEN
	_bSessionRegistered := _bCommActive := FALSE;
	// maybe new device, reset init status
	IF (NOT bAutoReconnect) THEN
		_bInitialized := _bDeviceIdentified := FALSE;
		_uiInitialize := 0;
	END_IF
END_IF

// no point in continuing
IF (NOT _tcpClient.xActive) THEN
	_sStatus := 'Not Active';
	RETURN;
END_IF
_sStatus := 'Active';
_uiRetry := 0;

// this is our state machine to handle various tasks
CASE _uiCOTP OF
	eCOTP.nop:
		bBusy := FALSE;
	eCOTP.connectRequest:
		IF (NOT _bTcpWriteData) THEN // write once by copying into buffer
			_stConnectReqRes := _stBuildConnectRequest(uiSize=>_uiTcpWriteSize);
			SysMemCpy(pDest:=ADR(_abTcpWriteBuffer), pSrc:=ADR(_stConnectReqRes), udiCount:=_uiTcpWriteSize);
			_bTcpWriteData := _bTcpWriteEn := TRUE;
		ELSIF (_tcpRead.xReady) THEN // data available
			IF (NOT _bSessionRegistered) THEN
				SysMemCpy(pDest:=ADR(_stConnectReqRes), pSrc:=ADR(_abTcpReadBuffer), udiCount:=_uiTcpReadSize);
				IF (_stConnectReqRes.connectCOTP.cotp.pduType = eCOTP.connectConfirm) THEN
					_bSessionRegistered := TRUE;
					// setup communication
					_stSetupCommReq := _stBuildSetupComm(uiSize=>_uiTcpWriteSize);
					SysMemCpy(pDest:=ADR(_abTcpWriteBuffer), pSrc:=ADR(_stSetupCommReq), udiCount:=_uiTcpWriteSize);
					_bTcpWriteEn := TRUE;
				END_IF
			ELSE
				SysMemCpy(pDest:=ADR(_stSetupCommRes), pSrc:=ADR(_abTcpReadBuffer), udiCount:=_uiTcpReadSize);
				IF (_stSetupCommRes.commHeader.ROSCTR = eROSCTR.ackData) THEN
					IF (_stSetupCommRes.errorCode = eParamErrorCode.noError) THEN
						_bSetupComm := TRUE;
					ELSE
						_bError := _bBuildMessage(sMessage1:=_sHeader, sMessage2:=_sGetErrorClass(_stSetupCommRes.errorClass), pbBuffer:=ADR(_sError));
					END_IF
					// reset
					_bTcpWriteData := FALSE;
					_uiCOTP := eCOTP.nop;
				END_IF
			END_IF
		END_IF
	eCOTP.disconnectRequest:
		IF (NOT _bTcpWriteData) THEN // write once by copying into buffer
			_stDisconnectReq := _stBuildDisconnectRequest(uiSize=>_uiTcpWriteSize);
			SysMemCpy(pDest:=ADR(_abTcpWriteBuffer), pSrc:=ADR(_stDisconnectReq), udiCount:=_uiTcpWriteSize);
			_bTcpWriteEn := TRUE;
			 // reset
			_bCommActive := _bSetupComm := _bTcpWriteData := _bSessionRegistered := FALSE;
			_uiCOTP := eCOTP.nop;
		END_IF
	eCOTP.data: // read, write
		CASE _uiROSCTR OF
			eROSCTR.job: // send
				CASE _uiFunction OF
					eFunction.readVariable:
						IF (NOT _bTcpWriteData) THEN // write once by copying into buffer
							_bTcpWriteData := _bTcpWriteEn := TRUE;
						ELSIF (_tcpRead.xReady) THEN // data available
							// strict matching COTP, ROSCTR, and method type
							SysMemCpy(pDest:=ADR(_stReadDbRes), pSrc:=ADR(_abTcpReadBuffer), udiCount:=SIZEOF(_stReadDbRes));
							IF (_stReadDbRes.rosctrCOTP.pduType = _uiCOTP 
								AND _stReadDbRes.commHeader.ROSCTR = eROSCTR.ackData 
								AND _stReadDbRes.paramReadVar.func = eFunction.readVariable 
								AND _stReadDbRes.errorClass = eErrorClass.noError
								AND _stReadDbRes.errorCode = 16#00
							) THEN
								bReadSuccess := TRUE;
							END_IF
							// reset
							_bTcpWriteData := FALSE;
							_uiCOTP := eCOTP.nop;
						END_IF
					eFunction.writeVariable:
						IF (NOT _bTcpWriteData) THEN // write once by copying into buffer
							_bTcpWriteData := _bTcpWriteEn := TRUE;
						ELSIF (_tcpRead.xReady) THEN // data available
							// strict matching COTP, ROSCTR, and method type
							SysMemCpy(pDest:=ADR(_stWriteDbRes), pSrc:=ADR(_abTcpReadBuffer), udiCount:=SIZEOF(_stWriteDbRes));
							IF (_stWriteDbRes.rosctrCOTP.pduType = _uiCOTP
								AND _stWriteDbRes.commHeader.ROSCTR = eROSCTR.ackData 
								AND _stWriteDbRes.paramVar.func = eFunction.writeVariable 
								AND _stWriteDbRes.errorClass = eErrorClass.noError
								AND _stWriteDbRes.errorCode = 16#00
								AND _stWriteDbRes.returnCode = eReturnCode.success
							) THEN
								// copy to struct to parse data
								THIS^._bWriteSuccess := TRUE;
							END_IF
							// reset
							_bTcpWriteData := FALSE;
							_uiCOTP := eCOTP.nop;
						END_IF
				END_CASE
			eROSCTR.userData:
				CASE _uiFunctionGroup OF
					eFunctionGroup.cpuFunctionsReq:
						CASE _uiSubfunction OF
							eCpuSubfunction.readSZL: // get module info
								CASE _uiSZL OF
									eSystemStateList.cpuId:
										IF (NOT _bTcpWriteData) THEN // write once by copying into buffer
											_bBuildGetCpu(eInfoType:=eSystemStateList.cpuId,
														pstReadSzlReq:=ADR(_stReadSzlReq),
														uiSize=>_uiTcpWriteSize);
											SysMemCpy(pDest:=ADR(_abTcpWriteBuffer), pSrc:=ADR(_stReadSzlReq), udiCount:=_uiTcpWriteSize);
											_bTcpWriteData := _bTcpWriteEn := TRUE;
										ELSIF (_tcpRead.xReady) THEN // data available
											// strict matching COTP, ROSCTR, and method type
											SysMemCpy(pDest:=ADR(_stReadSzlRes), pSrc:=ADR(_abTcpReadBuffer), udiCount:=SIZEOF(_stReadSzlRes));
											IF (_stReadSzlRes.rosctrCOTP.pduType = _uiCOTP
												AND _stReadSzlRes.commHeader.ROSCTR = eROSCTR.userData 
												AND _stReadSzlRes.userDataParam.functionGroup = eFunctionGroup.cpuFunctionsRes
												AND _stReadSzlRes.userDataParam.subfunction = eCpuSubfunction.readSZL
												AND _stReadSzlRes.szlId = _stReadSzlReq.szlId // has same endian
											) THEN
												// point to struct to parse data
												_bParseGetCpu(pbTcpReadBuffer:=ADR(_abTcpReadBuffer),
															uiTcpReadSize:=_uiTcpReadSize,
															eInfoType:=eSystemStateList.cpuId,
															pstCpuInfo:=ADR(_stCpuInfo));
												_bDeviceIdentified := TRUE;
											END_IF
											// reset
											_bTcpWriteData := FALSE;
											_uiCOTP := eCOTP.nop;
										END_IF
									eSystemStateList.cpuState:
										IF (NOT _bTcpWriteData) THEN // write once by copying into buffer
											_bBuildGetCpu(eInfoType:=eSystemStateList.cpuState,
														pstReadSzlReq:=ADR(_stReadSzlReq),
														uiSize=>_uiTcpWriteSize);
											SysMemCpy(pDest:=ADR(_abTcpWriteBuffer), pSrc:=ADR(_stReadSzlReq), udiCount:=_uiTcpWriteSize);
											_bTcpWriteData := _bTcpWriteEn := TRUE;
										ELSIF (_tcpRead.xReady) THEN // data available
											// strict matching COTP, ROSCTR, and method type
											SysMemCpy(pDest:=ADR(_stReadSzlRes), pSrc:=ADR(_abTcpReadBuffer), udiCount:=SIZEOF(_stReadSzlRes));
											IF (_stReadSzlRes.rosctrCOTP.pduType = _uiCOTP
												AND _stReadSzlRes.commHeader.ROSCTR = eROSCTR.userData 
												AND _stReadSzlRes.userDataParam.functionGroup = eFunctionGroup.cpuFunctionsRes
												AND _stReadSzlRes.userDataParam.subfunction = eCpuSubfunction.readSZL
												AND _stReadSzlRes.szlId = _stReadSzlReq.szlId // has same endian
											) THEN
												// point to struct to parse data
												_bParseGetCpu(pbTcpReadBuffer:=ADR(_abTcpReadBuffer),
															uiTcpReadSize:=_uiTcpReadSize,
															eInfoType:=eSystemStateList.cpuState,
															pstCpuInfo:=ADR(_stCpuInfo));
												_bDeviceState := TRUE;
											END_IF
											// reset
											_bTcpWriteData := FALSE;
											_uiCOTP := eCOTP.nop;
										END_IF
								END_CASE

						END_CASE
					eFunctionGroup.timeFunctionsReq:
						CASE _uiSubfunction OF
							eTimeSubfunction.readClock:
								IF (NOT _bTcpWriteData) THEN // write once by copying into buffer
									_bTcpWriteData := _bTcpWriteEn := TRUE;
								ELSIF (_tcpRead.xReady) THEN // data available
									// strict matching COTP, ROSCTR, and method type
									SysMemCpy(pDest:=ADR(_stClockRes), pSrc:=ADR(_abTcpReadBuffer), udiCount:=SIZEOF(_stClockRes));
									IF (_stClockRes.rosctrCOTP.pduType = _uiCOTP
										AND _stClockRes.commHeader.ROSCTR = eROSCTR.userData 
										AND _stClockRes.userDataParam.functionGroup = eFunctionGroup.timeFunctionsRes
										AND _stClockRes.userDataParam.subfunction = eTimeSubfunction.readClock
									) THEN
										bReadSuccess := TRUE;
									END_IF
									// reset
									_bTcpWriteData := FALSE;
									_uiCOTP := eCOTP.nop;
								END_IF
							eTimeSubfunction.setClock:
								IF (NOT _bTcpWriteData) THEN // write once by copying into buffer
									_bTcpWriteData := _bTcpWriteEn := TRUE;
								ELSIF (_tcpRead.xReady) THEN // data available
									// strict matching COTP, ROSCTR, and method type
									SysMemCpy(pDest:=ADR(_stClockRes), pSrc:=ADR(_abTcpReadBuffer), udiCount:=SIZEOF(_stClockRes));
									IF (_stClockRes.rosctrCOTP.pduType = _uiCOTP
										AND _stClockRes.commHeader.ROSCTR = eROSCTR.userData 
										AND _stClockRes.userDataParam.functionGroup = eFunctionGroup.timeFunctionsRes
										AND _stClockRes.userDataParam.subfunction = eTimeSubfunction.setClock
									) THEN
										_bWriteSuccess := TRUE;
									END_IF
									// reset
									_bTcpWriteData := FALSE;
									_uiCOTP := eCOTP.nop;
								END_IF
						END_CASE
				END_CASE
		END_CASE
END_CASE

// initialization
IF (_bTcpConnected AND (NOT _bError)) THEN
	CASE _uiInitialize OF
		0: // register session
			IF (NOT _bSessionRegistered OR NOT _bSetupComm) THEN
				_bConnectRequest();
			ELSE
				_bResetCommand();
				_uiInitialize := _uiInitialize + 1;
			END_IF
		1: // get CPU info
			IF (NOT _bDeviceIdentified) THEN
				bGetCpuInfo();
				_sStatus := 'Identifying device';
			ELSE
				_bResetCommand();
				_uiInitialize := _uiInitialize + 1;
			END_IF
		2: // get CPU state
			IF (NOT _bDeviceState) THEN
				bGetCpuState();
				_sStatus := 'Getting CPU state';
			ELSE
				_bResetCommand();
				_uiInitialize := _uiInitialize + 1;
			END_IF
		3: // finished initialization
			_bResetCommand();
			_bInitialized := _bCommActive := TRUE;
			_uiInitialize := _uiInitialize + 1;
		99: // some kind of error
			_sError := 'Init error';
			_bInitialized := FALSE;
	END_CASE
END_IF

// busy flag
bBusy := (_uiCOTP <> 0);

// write interval for each request
_tonTcpWriteWatchDog(IN:=(_bTcpWriteData OR (_uiCOTP <> 0)),
						PT:=_tCommWriteTimeout,
						Q=> ,
						ET=> );
IF (_tonTcpWriteWatchDog.Q) THEN
	_sError := 'Write request timed out, toggle bEnable to restart';
	IF (NOT _bInitialized) THEN
		_uiInitialize := 0; // reinitalize
		_bResetCommand(); // reset service calls
	END_IF
END_IF

// always reading
_tcpRead(xEnable:=(_tcpClient.xActive AND (NOT _tcpRead.xError)),
		xDone=> ,
		xBusy=> ,
		xError=> ,
		hConnection:=_tcpClient.hConnection,
		szSize:=SIZEOF(_abTcpReadBuffer),
		pData:=ADR(_abTcpReadBuffer),
		eError=> ,
		xReady=> ,
		szCount=>);
// get number of actual read bytes
IF (_tcpRead.xReady) THEN
	_uiTcpReadSize := CAA.SIZE_TO_UINT(szValue:=_tcpRead.szCount);
END_IF

// write when needed
_tcpWrite(xExecute:=_bTcpWriteEn,
			udiTimeOut:=_udiTcpWriteTimeout,
			xDone=> ,
			xBusy=> ,
			xError=> ,
			hConnection:=_tcpClient.hConnection,
			szSize:=_uiTcpWriteSize, // specified by each function
			pData:=ADR(_abTcpWriteBuffer),
			eError=> );
// if finished writing
IF (_tcpWrite.xDone) THEN
	_bTcpWriteEn:= FALSE;
END_IF

IF(_tcpRead.xError) THEN
	_bError := _bBuildMessage(sMessage1:='TCP Read Error; ', pbBuffer:=ADR(_sError));
END_IF
IF(_tcpWrite.eError <> 0) THEN
	_bError := _bBuildMessage(sMessage1:='TCP Write Error; ', pbBuffer:=ADR(_sError));
END_IF
-------------------------------------------------------------------------------
_bBuildMessage
-------------------------------------------------------------------------------

                  

Purpose: Copies sMessage1 and sMessage2 to pbBuffer

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _bBuildMessage: BOOL

VAR_INPUT
    sMessage1: string;
    sMessage2: string;
    pbBuffer: pointer;
END_VAR
bStringConcat(pbFrom:= ADR(sMessage1), pbTo:=pbBuffer, udiBufferSize:=SIZEOF(sMessage1));
bStringConcat(pbFrom:= ADR(sMessage2), pbTo:=pbBuffer, udiBufferSize:=SIZEOF(sMessage1));
_bBuildMessage := TRUE;
-------------------------------------------------------------------------------
FB_init
-------------------------------------------------------------------------------

                  

Purpose: Initialize an instance

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD FB_init: BOOL

VAR_INPUT
    bInitRetains: BOOL;
    bInCopyCode: BOOL;
    sIpAddress: string;
    uiPort: UINT;
    bRack: BYTE;
    bSlot: BYTE;
END_VAR
// initialize objects
THIS^._stConnectReqRes.connectCOTP.cotp.destRef := 16#0000;
THIS^._stConnectReqRes.connectCOTP.cotp.srcRef := 16#0001;
THIS^._bSessionRegistered := FALSE;
THIS^._udiTcpWriteTimeout := gvcParameters.udiTcpWriteTimeout;
THIS^._tTcpClientRetry := TO_TIME(gvcParameters.udiTcpClientRetry);
THIS^._udiTcpClientTimeout := gvcParameters.udiTcpClientTimeout;
THIS^._tCommWriteTimeout := TO_TIME(gvcParameters.uiCommWriteTimeout);
THIS^._bHexPrefix := gvcParameters.bHexPrefix;
THIS^._tcpIpAddr.sAddr := THIS^._sIpAddress := sIpAddress;
THIS^._uiPort := THIS^._uiPort := uiPort;

FB_init := TRUE;
-------------------------------------------------------------------------------
bGetPlcTime
-------------------------------------------------------------------------------

                  

Purpose: Request time from device

* Author: NothinRandom
* v1.0	December 16, 2021
* Note: Only works for models < S7-1200




METHOD bGetPlcTime: BOOL

VAR
    _uiYear: UINT;
    _uiMillisecond: UINT;
END_VAR
// call main
THIS^();

IF ((NOT THIS^._bCommActive) OR THIS^._bError) THEN
	RETURN;
END_IF

// acknowledge successful request
IF (THIS^.bReadSuccess 
	AND THIS^._uiROSCTR = eROSCTR.userData 
	AND THIS^._uiFunctionGroup = eFunctionGroup.timeFunctionsReq
	AND THIS^._stClockRes.commHeader.ROSCTR = eROSCTR.userData
	AND THIS^._stClockRes.userDataParam.methodType = eParamMethod.response
	AND THIS^._stClockRes.userDataParam.functionGroup = eFunctionGroup.timeFunctionsRes
	AND THIS^._stClockRes.userDataParam.subfunction = eTimeSubfunction.readClock
) THEN
	// parse if no error
	IF (THIS^._stClockRes.errorCode = eParamErrorCode.noError) THEN
		THIS^._stTimestamp := THIS^._stClockRes.timestamp;
		
		IF (bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.year2)) < 90) THEN
			_uiYear := bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.year2)) + 2000;
		ELSE
			_uiYear := bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.year2)) + 1900;
		END_IF
		_uiMillisecond := bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.milliDow[0])) * 10
						+ bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.milliDow[1])) / 10;
		THIS^._ldtPlcTime := ldtCreateFromParam(uiYear:=_uiYear,
												uiMonth:=bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.month)),
												uiDay:=bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.day)),
												uliHour:=bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.hour)),
												uliMinute:=bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.minute)),
												uliSsecond:=bBcdToDec(pbInput:=ADR(THIS^._stTimestamp.second)),
												uliMSecond:=_uiMillisecond);
		THIS^._ltPlcTime := LDT_TO_LTIME(THIS^._ldtPlcTime);
	END_IF
	THIS^.bReadSuccess := FALSE;
	bGetPlcTime := TRUE;
	RETURN;
END_IF

// execute when available
IF (THIS^._uiCOTP = eCOTP.nop) THEN
	// prepare our request
	THIS^._uiCOTP := eCOTP.data;
	THIS^._uiROSCTR := eROSCTR.userData;
	THIS^._uiFunctionGroup := eFunctionGroup.timeFunctionsReq;
	THIS^._uiSubfunction := eTimeSubfunction.readClock;
	// set total length to big endian
	THIS^._stClockReq.tpkt.length := THIS^._uiTcpWriteSize := SIZEOF(THIS^._stClockReq) - SIZEOF(THIS^._stClockReq.timestamp);
	THIS^._stClockReq.tpkt.length := uiSwap(pbInput:=ADR(THIS^._stClockReq.tpkt.length));
	// set comm header
	THIS^._stClockReq.commHeader.ROSCTR := eROSCTR.userData;
	THIS^._stClockReq.commHeader.PDUR := 16#0038;
	 // set parameter length as big endian
	THIS^._stClockReq.commHeader.paramLen := SIZEOF(THIS^._stClockReq.userDataParam);
	THIS^._stClockReq.commHeader.paramLen := uiSwap(pbInput:=ADR(THIS^._stClockReq.commHeader.paramLen));
	// set data length as big endian
	THIS^._stClockReq.commHeader.dataLen := SIZEOF(THIS^._stClockReq.commData);
	THIS^._stClockReq.commHeader.dataLen := uiSwap(pbInput:=ADR(THIS^._stClockReq.commHeader.dataLen));
	THIS^._stClockReq.userDataParam.methodType := eParamMethod.request;
	THIS^._stClockReq.userDataParam.functionGroup := eFunctionGroup.timeFunctionsReq;
	THIS^._stClockReq.userDataParam.subfunction := eTimeSubfunction.readClock;
	THIS^._stClockReq.commData.returnCode := eReturnCode.objectDoesNotExist;
	THIS^._stClockReq.commData.transportSize := eDataTransportSize._NULL;

	// copy to buffer
	SysMemCpy(pDest:=ADR(THIS^._abTcpWriteBuffer),
			pSrc:=ADR(THIS^._stClockReq),
			udiCount:=THIS^._uiTcpWriteSize
	);
	// ship it
	THIS^.bReadSuccess := FALSE;
END_IF
-------------------------------------------------------------------------------
bDisconnect
-------------------------------------------------------------------------------

                  

Purpose: Request to disconnect

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD bDisconnect: BOOL
// ignore if inactive or error
IF ((NOT THIS^._bCommActive) OR THIS^._bError) THEN
	RETURN;
END_IF

// execute when available
IF (THIS^._uiCOTP = eCOTP.nop) THEN
	THIS^._uiCOTP := eCOTP.disconnectRequest;
	bDisconnect := TRUE;
	THIS^();
END_IF
-------------------------------------------------------------------------------
bResetFault
-------------------------------------------------------------------------------

                  

Purpose: Acknowledge fault and reset

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD bResetFault: BOOL
// reset fault flags
THIS^.bError := THIS^._bError := FALSE;
THIS^.sError := THIS^._sError := THIS^.sStatus := THIS^._sStatus := '';

bResetFault := TRUE;

// call main
THIS^();
-------------------------------------------------------------------------------
_bResetFB
-------------------------------------------------------------------------------

                  

Purpose: Reset various FB variables

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _bResetFB: BOOL
// reset status flags
THIS^.bTcpActive := THIS^.bCommActive := THIS^.bBusy := THIS^.bReadSuccess := THIS^.bError := FALSE;
THIS^._bError := THIS^._bInitialized := FALSE;
THIS^.sError := THIS^._sError := THIS^.sStatus := THIS^._sStatus := '';

// reset connection flags
THIS^._bSessionRegistered := THIS^._bCommActive := THIS^._bWriteSuccess := THIS^._bDeviceIdentified := THIS^._bDeviceState := FALSE;
THIS^._hTcpConnection := THIS^._uiInitialize := 16#00;
THIS^._uiCOTP := THIS^._uiROSCTR := THIS^._uiFunctionGroup := THIS^._uiSubfunction := THIS^._uiFunction := 16#00;

// reset TCP flags
THIS^._tcpClient.xEnable := THIS^._bEnable := THIS^._bTcpConnected := FALSE;
THIS^._tcpWrite.xExecute := THIS^._bTcpWriteEn := THIS^._bTcpWriteData := FALSE;
THIS^._tcpRead.xEnable := THIS^._bRead := FALSE;
_tcpClient();
_tcpWrite();
_tcpRead();

// reset buffer
SysMemSet(pDest:=ADR(THIS^._abTcpReadBuffer), udiValue:=0, udiCount:=100);
SysMemSet(pDest:=ADR(THIS^._abTcpWriteBuffer), udiValue:=0, udiCount:=100);

_bResetFB := TRUE;
-------------------------------------------------------------------------------
_sGetErrorClass
-------------------------------------------------------------------------------

                  

Purpose: Return error class in response (e.g. stReadDbRes, stWriteDbRes)

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _sGetErrorClass: string

VAR_INPUT
    bErrorClass: BYTE;
END_VAR
CASE bErrorClass OF 
	eErrorClass.noError:
		_sGetErrorClass := 'No Error';
	eErrorClass.applicationRelationship:
		_sGetErrorClass := 'Application relationship';
	eErrorClass.objectDefinition:
		_sGetErrorClass := 'Object definition';
	eErrorClass.noResourceAvailable:
		_sGetErrorClass := 'No resources available';
	eErrorClass.errorOnServiceProcessing:
		_sGetErrorClass := 'Error on service processing';
	eErrorClass.errorOnSupplies:
		_sGetErrorClass := 'Error on supplies';
	eErrorClass.accessError:
		_sGetErrorClass := 'Access error';
	ELSE
		_sGetErrorClass := 'Unknown';
END_CASE
-------------------------------------------------------------------------------
_stBuildConnectRequest
-------------------------------------------------------------------------------

                  

Purpose: Build the ConnectRequest packet

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _stBuildConnectRequest: stConnectReqRes

VAR_OUTPUT
    uiSize: UINT;
END_VAR
_stBuildConnectRequest.tpkt.length := SIZEOF(_stBuildConnectRequest.tpkt) + SIZEOF(_stBuildConnectRequest.connectCOTP);
_stBuildConnectRequest.connectCOTP.cotp.length := TO_BYTE(SIZEOF(_stBuildConnectRequest.connectCOTP) - 1); // ignore first byte for length
// destTSAP has base of 16#0100, each rack offsets by 32 (16#20) and slot by 1 (16#01)
_stBuildConnectRequest.connectCOTP.destTSAP := _stBuildConnectRequest.connectCOTP.destTSAP + THIS^._bRack*32 + THIS^._bSlot;
SysMemForceSwap(pbyBuffer:=ADR(_stBuildConnectRequest.tpkt.length), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildConnectRequest.connectCOTP.cotp.destRef), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildConnectRequest.connectCOTP.cotp.srcRef), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildConnectRequest.connectCOTP.srcTSAP), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildConnectRequest.connectCOTP.destTSAP), udiSize:=2, udiCount:=1);
uiSize := SIZEOF(_stBuildConnectRequest);
-------------------------------------------------------------------------------
bGetCpuInfo
-------------------------------------------------------------------------------

                  

Purpose: Get CPU module info
		 Useful to "scan" your local network

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD bGetCpuInfo: BOOL
// call main if we are initialized,
// else stack overflow
IF (THIS^._bInitialized AND THIS^._uiInitialize <> 0) THEN
	THIS^();
END_IF

// requires TCP active
IF ((NOT THIS^._bTcpConnected) OR THIS^._bError) THEN
	RETURN;
END_IF

// write only when available
IF (THIS^._uiCOTP = eCOTP.nop) THEN
	THIS^._uiCOTP := eCOTP.data;
	THIS^._uiROSCTR := eROSCTR.userData;
	THIS^._uiFunctionGroup := eFunctionGroup.cpuFunctionsReq;
	THIS^._uiSubfunction := eCpuSubfunction.readSZL;
	THIS^._uiSZL := eSystemStateList.cpuId;
	THIS^._bDeviceIdentified := FALSE;
	bGetCpuInfo := TRUE;
END_IF
-------------------------------------------------------------------------------
_bConnectRequest
-------------------------------------------------------------------------------

                  

Purpose: Request COTP Register Session

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _bConnectRequest: BOOL

VAR_OUTPUT
    uiSize: UINT;
END_VAR
// only perform if no active session and TCP is active
IF (_bSessionRegistered OR (NOT _bTcpConnected)) THEN
	RETURN;
END_IF

// write only when available
IF (THIS^._uiCOTP = eCOTP.nop) THEN
	_uiCOTP := eCOTP.connectRequest;
	_bConnectRequest := TRUE;
END_IF
-------------------------------------------------------------------------------
_bResetCommand
-------------------------------------------------------------------------------

                  

Purpose: Reset COTP command

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _bResetCommand: BOOL
THIS^._uiCOTP := eCOTP.nop;

_bResetCommand := TRUE;
-------------------------------------------------------------------------------
bSetPlcTime
-------------------------------------------------------------------------------

                  

Purpose: Set PLC time with specified time

* Author: NothinRandom
* v1.0	December 16, 2021
* Note: Only works for models < S7-1200




METHOD bSetPlcTime: BOOL

VAR_INPUT
    uliTime: ULINT;
END_VAR

VAR
    _ldtNow: LDATE_AND_TIME;
    _uiTemp: UINT;
    _bTemp: BYTE;
END_VAR
// call main
THIS^();

IF ((NOT THIS^._bCommActive) OR THIS^._bError) THEN
	RETURN;
END_IF

// acknowledge successful write
IF (THIS^._bWriteSuccess
	AND THIS^._uiROSCTR = eROSCTR.userData 
	AND THIS^._uiFunctionGroup = eFunctionGroup.timeFunctionsReq
	AND THIS^._stClockRes.commHeader.ROSCTR = eROSCTR.userData
	AND THIS^._stClockRes.userDataParam.methodType = eParamMethod.response
	AND THIS^._stClockRes.userDataParam.functionGroup = eFunctionGroup.timeFunctionsRes
	AND THIS^._stClockRes.userDataParam.subfunction = eTimeSubfunction.setClock
) THEN
	THIS^._bWriteSuccess := FALSE;
	bSetPlcTime := TRUE;
	RETURN;
END_IF

// execute when available
IF (THIS^._uiCOTP = eCOTP.nop) THEN
	// prepare our request
	THIS^._uiCOTP := eCOTP.data;
	THIS^._uiROSCTR := eROSCTR.userData;
	THIS^._uiFunctionGroup := eFunctionGroup.timeFunctionsReq;
	THIS^._uiSubfunction := eTimeSubfunction.setClock;
	THIS^._stClockReq.commHeader.ROSCTR := eROSCTR.userData;
	THIS^._stClockReq.commHeader.PDUR := 16#0389;
	// set total length to big endian
	THIS^._stClockReq.tpkt.length := THIS^._uiTcpWriteSize := SIZEOF(THIS^._stClockReq);
	THIS^._stClockReq.tpkt.length := uiSwap(pbInput:=ADR(THIS^._stClockReq.tpkt.length));
	 // set parameter length as big endian
	THIS^._stClockReq.commHeader.paramLen := SIZEOF(THIS^._stClockReq.userDataParam);
	THIS^._stClockReq.commHeader.paramLen := uiSwap(pbInput:=ADR(THIS^._stClockReq.commHeader.paramLen));
	// set data length as big endian
	THIS^._stClockReq.commHeader.dataLen := SIZEOF(THIS^._stClockReq.commData) + SIZEOF(THIS^._stClockReq.timestamp);
	THIS^._stClockReq.commHeader.dataLen := uiSwap(pbInput:=ADR(THIS^._stClockReq.commHeader.dataLen));
	THIS^._stClockReq.userDataParam.methodType := eParamMethod.request;
	THIS^._stClockReq.userDataParam.functionGroup := eFunctionGroup.timeFunctionsReq;
	THIS^._stClockReq.userDataParam.subfunction := eTimeSubfunction.setClock;
	THIS^._stClockReq.commData.returnCode := eReturnCode.success;
	THIS^._stClockReq.commData.transportSize := eDataTransportSize._OCTET_STRING;
	// set comm data length to big endian
	THIS^._stClockReq.commData.length := SIZEOF(THIS^._stClockReq.timestamp);
	THIS^._stClockReq.commData.length := uiSwap(pbInput:=ADR(THIS^._stClockReq.commData.length));

	// 0 means synchronize with CoDeSys time
	IF (uliTime = 0) THEN
		THIS^._timestamp.Now();
		_ldtNow := THIS^._timestamp.ldtNow;
	ELSE
		IF (uliTime < 2000000000) THEN // seconds
			uliTime := uliTime * 1000000000;
		ELSIF (uliTime < 2000000000000) THEN // milliseconds
			uliTime := uliTime * 1000000;
		ELSIF (uliTime < 2000000000000000) THEN // microseconds
			uliTime := uliTime * 1000;
		END_IF
		_ldtNow := TO_LDT(uliTime);
	END_IF
	THIS^._stClockReq.timestamp.reserved := 16#00;
	THIS^._stClockReq.timestamp.year1 := 16#19; // not sure what this is used for
	_uiTemp := uiFromLDT(eType:=eFromLDT.year, ldtInput:=_ldtNow);
	IF (_uiTemp > 2000) THEN
		_uiTemp := _uiTemp - 2000;
	ELSE
		_uiTemp := _uiTemp - 1900;
	END_IF
	_bTemp := TO_BYTE(_uiTemp);
	THIS^._stClockReq.timestamp.year2 := bDecToBcd(pbInput:=ADR(_bTemp));
	_bTemp := TO_BYTE(uiFromLDT(eType:=eFromLDT.month, ldtInput:=_ldtNow));
	THIS^._stClockReq.timestamp.month := bDecToBcd(pbInput:=ADR(_bTemp));
	_bTemp := TO_BYTE(uiFromLDT(eType:=eFromLDT.day, ldtInput:=_ldtNow));
	THIS^._stClockReq.timestamp.day := bDecToBcd(pbInput:=ADR(_bTemp));
	_bTemp := TO_BYTE(uiFromLDT(eType:=eFromLDT.hour, ldtInput:=_ldtNow));
	THIS^._stClockReq.timestamp.hour := bDecToBcd(pbInput:=ADR(_bTemp));
	_bTemp := TO_BYTE(uiFromLDT(eType:=eFromLDT.minute, ldtInput:=_ldtNow));
	THIS^._stClockReq.timestamp.minute := bDecToBcd(pbInput:=ADR(_bTemp));
	_bTemp := TO_BYTE(uiFromLDT(eType:=eFromLDT.second, ldtInput:=_ldtNow));
	THIS^._stClockReq.timestamp.second := bDecToBcd(pbInput:=ADR(_bTemp));
	_uiTemp := uiFromLDT(eType:=eFromLDT.millisecond, ldtInput:=_ldtNow);
	_bTemp := TO_BYTE(_uiTemp/10);
	THIS^._stClockReq.timestamp.milliDow[0] := bDecToBcd(pbInput:=ADR(_bTemp));
	_bTemp := TO_BYTE((_uiTemp MOD 10) * 10 + uiFromLDT(eType:=eFromLDT.dayOfWeek, ldtInput:=_ldtNow));
	THIS^._stClockReq.timestamp.milliDow[1] := bDecToBcd(pbInput:=ADR(_bTemp));

	// copy to buffer
	SysMemCpy(pDest:=ADR(THIS^._abTcpWriteBuffer),
			pSrc:=ADR(THIS^._stClockReq),
			udiCount:=SIZEOF(THIS^._stClockReq)
	);

	// ship it
	THIS^._bWriteSuccess := FALSE;
END_IF
-------------------------------------------------------------------------------
_stBuildSetupComm
-------------------------------------------------------------------------------

                  

Purpose: Build the request to setup communication packet

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _stBuildSetupComm: stSetupCommReq

VAR_OUTPUT
    uiSize: UINT;
END_VAR
_stBuildSetupComm.tpkt.length := SIZEOF(_stBuildSetupComm.tpkt) + SIZEOF(_stBuildSetupComm.cotp) + SIZEOF(_stBuildSetupComm.commHeader) + SIZEOF(_stBuildSetupComm.commParam);
_stBuildSetupComm.cotp.length := TO_BYTE(SIZEOF(_stBuildSetupComm.cotp) - 1); // ignore first byte for length
_stBuildSetupComm.commHeader.protocolId := 16#32;
_stBuildSetupComm.commHeader.ROSCTR := eROSCTR.job;
_stBuildSetupComm.commHeader.paramLen := SIZEOF(_stBuildSetupComm.commParam);
_stBuildSetupComm.commParam.func := eFunction.setupCommunication;
// le sighs, Siemens is big endian
SysMemForceSwap(pbyBuffer:=ADR(_stBuildSetupComm.tpkt.length), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildSetupComm.commHeader.redId), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildSetupComm.commHeader.PDUR), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildSetupComm.commHeader.paramLen), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildSetupComm.commHeader.dataLen), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildSetupComm.commParam.maxCalling), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildSetupComm.commParam.maxCalled), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(_stBuildSetupComm.commParam.pduLen), udiSize:=2, udiCount:=1);
uiSize := SIZEOF(_stBuildSetupComm);
-------------------------------------------------------------------------------
_stBuildDisconnectRequest
-------------------------------------------------------------------------------

                  

Purpose: Build the DisconnectRequest packet

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _stBuildDisconnectRequest: stDisconnectReq

VAR_OUTPUT
    uiSize: UINT;
END_VAR
// only perform if no active session and TCP is active
IF ((NOT _bSessionRegistered) OR (NOT _bTcpConnected)) THEN
	RETURN;
END_IF

_stBuildDisconnectRequest.tpkt.length := SIZEOF(_stBuildDisconnectRequest);
_stBuildDisconnectRequest.cotp.length := TO_BYTE(SIZEOF(_stBuildDisconnectRequest.cotp) - 1);
_stBuildDisconnectRequest.cotp.pduType := 16#80;
_stBuildDisconnectRequest.cotp.destRef := THIS^._stConnectReqRes.connectCOTP.cotp.destRef;
_stBuildDisconnectRequest.cotp.srcRef := THIS^._stConnectReqRes.connectCOTP.cotp.srcRef;
_stBuildDisconnectRequest.cotp.reason := 16#00;
SysMemForceSwap(pbyBuffer:=ADR(_stBuildDisconnectRequest.tpkt.length), udiSize:=2, udiCount:=1);
uiSize := SIZEOF(_stBuildDisconnectRequest);
-------------------------------------------------------------------------------
bReadDB
-------------------------------------------------------------------------------

                  

Purpose: Read PLC datablock (DB)

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD bReadDB: BOOL

VAR_INPUT
    uiIndex: UINT;
    udiOffset: UDINT;
    eDataSize: eDataType;
    pbBuffer: pointer;
    uiBufferSize: UINT;
    uiElements: UINT;
    psId: pointer;
END_VAR

VAR
    _uiOffset: UINT;
    _pbReadBuffer: pointer;
    _pbWriteBuffer: pointer;
    _uiIndex: UINT;
    _uiDataItemLen: UINT;
    _abEndianBuffer: ARRAY [..] OF ;
    _bConvert: BOOL;
    _bTerminate: BOOL;
    _pbTemp: pointer;
END_VAR
// call main
THIS^();

// ignore if inactive or error
IF ((NOT THIS^._bCommActive) OR THIS^._bError) THEN
	RETURN;
END_IF

// return on invalid parameters
IF (uiIndex < 1 OR uiBufferSize = 0) THEN
	// default Id
	IF (psId = 0) THEN
		psId := ADR('Read: ');
	END_IF
	THIS^._bError := _bBuildMessage(sMessage1:=psId^, sMessage2:='Invalid Input Params', pbBuffer:=ADR(THIS^._sError));
	RETURN;
END_IF

// write to output buffer when data is ready
IF (THIS^.bReadSuccess 
	AND THIS^._uiFunction = eFunction.readVariable 
	AND THIS^._uiROSCTR = eROSCTR.job 
	AND THIS^._stReadDbRes.commHeader.ROSCTR = eROSCTR.ackData
) THEN
	// default Id
	IF (psId = 0) THEN
		psId := ADR('Read: ');
	END_IF
	// build our response
	_pbReadBuffer := ADR(THIS^._abTcpReadBuffer);
	// get offset to copy data in
	_uiOffset := SIZEOF(THIS^._stReadDbRes);
	// extract data
	FOR _uiIndex := 1 TO THIS^._stReadDbRes.paramReadVar.itemCount DO
		SysMemCpy(pDest:=ADR(THIS^._stCommData),
			pSrc:=(_pbReadBuffer+_uiOffset),
			udiCount:=SIZEOF(THIS^._stCommData)
		);
		IF (THIS^._stCommData.returnCode = eReturnCode.success) THEN
			// length is sent in bits, need conversion to byte
			_uiDataItemLen := uiSwap(pbInput:=ADR(THIS^._stCommData.length)) / 8;
			_uiOffset := _uiOffset + SIZEOF(THIS^._stCommData);
			// need to swap from Big to Little endian here
			CASE eDataSize OF
				eDataType._BIT,
				eDataType._BOOL,
				eDataType._SINT,
				eDataType._USINT,
				eDataType._BYTE,
				eDataType._CHAR: // 1 byte
					uiBufferSize := 1;
					_bConvert := TRUE;
				eDataType._INT,
				eDataType._UINT,
				eDataType._WORD,
				eDataType._DATE,
				eDataType._S5Time: // 2 bytes
					uiBufferSize := 2;
					_bConvert := TRUE;
				eDataType._DINT,
				eDataType._UDINT,
				eDataType._DWORD,
				eDataType._REAL,
				eDataType._TIME,
				eDataType._TOD: // 4 bytes
					uiBufferSize := 4;
					_bConvert := TRUE;
				eDataType._LINT,
				eDataType._ULINT,
				eDataType._LWORD,
				eDataType._LREAL,
				eDataType._DATETIME: // 8 bytes
					uiBufferSize := 8;
					_bConvert := TRUE;
				eDataType._STRING: // 
					_pbTemp := _pbReadBuffer + _uiOffset; // locate where data starts
					_pbTemp := _pbTemp + 1; // actual len
					uiBufferSize := _pbTemp^ + 2; // offset of 2: max len, actual len
					_bTerminate := TRUE;
			END_CASE
			// swap endian on known data types
			IF (_bConvert) THEN
				SysMemCpy(pDest:=ADR(_abEndianBuffer),
						pSrc:=(_pbReadBuffer+_uiOffset),
						udiCount:=_uiDataItemLen);
				SysMemForceSwap(pbyBuffer:=ADR(_abEndianBuffer),
										udiSize:=_uiDataItemLen,
										udiCount:=1);
				// copy the endian buffer into buffer
				SysMemCpy(pDest:=pbBuffer, pSrc:=ADR(_abEndianBuffer), udiCount:=uiBufferSize);
			ELSIF (_bTerminate) THEN
				// only copy out up to actual len to speed up
				SysMemCpy(pDest:=pbBuffer, pSrc:=(_pbReadBuffer+_uiOffset), udiCount:=uiBufferSize);
				pbBuffer := pbBuffer + uiBufferSize + 1;
				pbBuffer^ := 16#00; // terminate
			ELSE
				// only copy data out to buffer
				SysMemCpy(pDest:=pbBuffer, pSrc:=(_pbReadBuffer+_uiOffset), udiCount:=_uiDataItemLen);
			END_IF
			_uiOffset := _uiOffset + _uiDataItemLen;
		END_IF
	END_FOR
	THIS^.bReadSuccess := FALSE;
	bReadDB := TRUE;
	RETURN;
END_IF

// execute when available
IF (THIS^._uiCOTP = eCOTP.nop) THEN
	// prepare our request
	THIS^._uiCOTP := eCOTP.data;
	THIS^._uiROSCTR := eROSCTR.job;
	THIS^._uiFunction := eFunction.readVariable;
	// set total length
	THIS^._stReadDbReq.tpkt.length := THIS^._uiTcpWriteSize := SIZEOF(THIS^._stWriteDbReq) + SIZEOF(THIS^._stParamItem);
	// swap to big endian
	THIS^._stReadDbReq.tpkt.length := uiSwap(pbInput:=ADR(THIS^._stReadDbReq.tpkt.length));
	THIS^._stReadDbReq.commHeader.ROSCTR := eROSCTR.job;
	THIS^._stReadDbReq.commHeader.PDUR := 16#0001; // 256
	// set param length to big endian
	THIS^._stReadDbReq.commHeader.paramLen := SIZEOF(THIS^._stReadDbReq.paramVar) + SIZEOF(THIS^._stParamItem);
	THIS^._stReadDbReq.commHeader.paramLen := uiSwap(pbInput:=ADR(THIS^._stReadDbReq.commHeader.paramLen));
	
	THIS^._stReadDbReq.paramVar.func := eFunction.readVariable;
	THIS^._stReadDbReq.paramVar.itemCount := 16#01;
	// set Parameter Item info
	THIS^._stParamItem.length := uiSwap(pbInput:=ADR(uiBufferSize)); // expected bytes to read
	THIS^._stParamItem.dbNumber := uiSwap(pbInput:=ADR(uiIndex));
	THIS^._stParamItem.area := eArea.dataBlocks;
	// take offset and multiple by 8 to fill out 24b
	udiOffset := udiOffset * 8; // bits offset
	THIS^._stParamItem.address[0] := TO_BYTE(SHR(udiOffset, 16) AND 16#FF);
	THIS^._stParamItem.address[1] := TO_BYTE(SHR(udiOffset, 8) AND 16#FF);
	THIS^._stParamItem.address[2] := TO_BYTE(SHR(udiOffset, 0) AND 16#FF);

	// build our request
	_pbWriteBuffer := ADR(THIS^._abTcpWriteBuffer);
	// copy header
	_uiOffset := SIZEOF(THIS^._stReadDbReq);
	SysMemCpy(pDest:=_pbWriteBuffer, pSrc:=ADR(THIS^._stReadDbReq), udiCount:=_uiOffset);
	_pbWriteBuffer := _pbWriteBuffer + _uiOffset;
	_uiOffset := SIZEOF(THIS^._stParamItem);
	SysMemCpy(pDest:=_pbWriteBuffer, pSrc:=ADR(THIS^._stParamItem), udiCount:=_uiOffset);
	// ship it
	THIS^.bReadSuccess := FALSE;
	// use default or specified Id 
	IF (psId = 0) THEN
		THIS^._sHeader := 'Read: ';
	ELSE
		THIS^._sHeader := psId^;
	END_IF
END_IF
-------------------------------------------------------------------------------
bWriteDB
-------------------------------------------------------------------------------

                  

Purpose: Write PLC datablock (DB)

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD bWriteDB: BOOL

VAR_INPUT
    uiIndex: UINT;
    udiOffset: UDINT;
    eDataSize: eDataType;
    pbBuffer: pointer;
    uiBufferSize: UINT;
    uiElements: UINT;
    psId: pointer;
END_VAR

VAR
    _uiOffset: UINT;
    _uiCount: UINT;
    _pbReadBuffer: pointer;
    _pbWriteBuffer: pointer;
    _uiIndex: UINT;
    _uiDataItemLen: UINT;
    _abEndianBuffer: ARRAY [..] OF ;
    _bConvert: BOOL;
    _pbTemp: pointer;
END_VAR
// call main
THIS^();

// ignore if inactive or error
IF (NOT THIS^._bCommActive OR THIS^._bError) THEN
	RETURN;
END_IF

// acknowledge successful write
IF (THIS^._bWriteSuccess
	AND THIS^._uiFunction = eFunction.writeVariable 
	AND THIS^._uiROSCTR = eROSCTR.job 
	AND THIS^._stWriteDbRes.commHeader.ROSCTR = eROSCTR.ackData
) THEN
	THIS^._bWriteSuccess := FALSE;
	bWriteDB := TRUE;
	RETURN;
END_IF

// execute when available
IF (THIS^._uiCOTP = eCOTP.nop) THEN
	// default Id
	IF (psId = 0) THEN
		psId := ADR('Write: ');
	END_IF
	// check pbBuffer
	IF (pbBuffer = 0) THEN
		THIS^._bError := _bBuildMessage(sMessage1:=psId^, sMessage2:='Invalid buffer', pbBuffer:=ADR(THIS^._sError));
		RETURN;
	END_IF
	// flag for swapping endian for known data type
	CASE eDataSize OF
		eDataType._BIT,
		eDataType._BOOL,
		eDataType._SINT,
		eDataType._USINT,
		eDataType._BYTE,
		eDataType._CHAR: // 1 byte
			uiBufferSize := 1;
			_bConvert := TRUE;
		eDataType._INT,
		eDataType._UINT,
		eDataType._WORD,
		eDataType._DATE,
		eDataType._S5Time: // 2 bytes
			uiBufferSize := 2;
			_bConvert := TRUE;
		eDataType._DINT,
		eDataType._UDINT,
		eDataType._DWORD,
		eDataType._REAL,
		eDataType._TIME,
		eDataType._TOD: // 4 bytes
			uiBufferSize := 4;
			_bConvert := TRUE;
		eDataType._LINT,
		eDataType._ULINT,
		eDataType._LWORD,
		eDataType._LREAL,
		eDataType._DATETIME: // 8 bytes
			uiBufferSize := 8;
			_bConvert := TRUE;
		eDataType._STRING: // actual len, total size
			_pbTemp := pbBuffer;
			_pbTemp^ := TO_BYTE(uiBufferSize - 3); // set max len
			_pbTemp := _pbTemp + 1;
			_pbTemp^ := TO_BYTE(Stu.StrLenA(pstData:=(_pbTemp+1))); // set actual len
	END_CASE
	// prepare our request
	THIS^._uiCOTP := eCOTP.data;
	THIS^._uiROSCTR := eROSCTR.job;
	THIS^._uiFunction := eFunction.writeVariable;
	// set total length
	THIS^._uiTcpWriteSize := SIZEOF(THIS^._stWriteDbReq) + SIZEOF(THIS^._stParamItem) + SIZEOF(THIS^._stCommData) + uiBufferSize;
	// ignore TPKT: version and reserved
	THIS^._stWriteDbReq.tpkt.length := THIS^._uiTcpWriteSize;
	// swap to big endian
	THIS^._stWriteDbReq.tpkt.length := uiSwap(pbInput:=ADR(THIS^._stWriteDbReq.tpkt.length));
	THIS^._stWriteDbReq.commHeader.ROSCTR := eROSCTR.job;
	THIS^._stWriteDbReq.commHeader.PDUR := 16#0001; // 256
	// set param length to big endian
	THIS^._stWriteDbReq.commHeader.paramLen := SIZEOF(THIS^._stWriteDbReq.paramVar) + SIZEOF(THIS^._stParamItem);
	THIS^._stWriteDbReq.commHeader.paramLen := uiSwap(pbInput:=ADR(THIS^._stWriteDbReq.commHeader.paramLen));
	// set data length to big endian
	THIS^._stWriteDbReq.commHeader.dataLen := SIZEOF(THIS^._stCommData) + uiBufferSize;
	THIS^._stWriteDbReq.commHeader.dataLen := uiSwap(pbInput:=ADR(THIS^._stWriteDbReq.commHeader.dataLen));
	// set parameter variable
	THIS^._stWriteDbReq.ParamVar.func := eFunction.writeVariable;
	THIS^._stWriteDbReq.paramVar.itemCount := 16#01;
	// set Parameter Item info
	THIS^._stParamItem.length := uiSwap(pbInput:=ADR(uiBufferSize)); // expected bytes to read
	THIS^._stParamItem.dbNumber := uiSwap(pbInput:=ADR(uiIndex));
	THIS^._stParamItem.area := eArea.dataBlocks;
	// take offset and multiple by 8 to fill out 24b
	udiOffset := udiOffset * 8; // bits offset
	THIS^._stParamItem.address[0] := TO_BYTE(SHR(udiOffset, 16) AND 16#FF);
	THIS^._stParamItem.address[1] := TO_BYTE(SHR(udiOffset, 8) AND 16#FF);
	THIS^._stParamItem.address[2] := TO_BYTE(SHR(udiOffset, 0) AND 16#FF); 
	// set communication data
	THIS^._stCommData.returnCode := eReturnCode.reserved;
	THIS^._stCommData.transportSize := eItemTransportSize._WORD;
	// set length to big endian
	THIS^._stCommData.length := 16#0001 * 8 * uiBufferSize; // only write one data type for now
	THIS^._stCommData.length := uiSwap(pbInput:=ADR(THIS^._stCommData.length));
	// build our request
	_pbWriteBuffer := ADR(THIS^._abTcpWriteBuffer);
	// copy header
	_uiOffset := SIZEOF(THIS^._stWriteDbReq);
	SysMemCpy(pDest:=_pbWriteBuffer, pSrc:=ADR(THIS^._stWriteDbReq), udiCount:=_uiOffset);
	_pbWriteBuffer := _pbWriteBuffer + _uiOffset;
	_uiOffset := SIZEOF(THIS^._stParamItem);
	SysMemCpy(pDest:=_pbWriteBuffer, pSrc:=ADR(THIS^._stParamItem), udiCount:=_uiOffset);
	_pbWriteBuffer := _pbWriteBuffer + _uiOffset;
	_uiOffset := SIZEOF(THIS^._stCommData);
	SysMemCpy(pDest:=_pbWriteBuffer, pSrc:=ADR(THIS^._stCommData), udiCount:=_uiOffset);
	_pbWriteBuffer := _pbWriteBuffer + _uiOffset;
	// swap endian on known data types
	IF (_bConvert) THEN
		SysMemCpy(pDest:=ADR(_abEndianBuffer),
				pSrc:=pbBuffer,
				udiCount:=uiBufferSize);
		SysMemForceSwap(pbyBuffer:=ADR(_abEndianBuffer),
						udiSize:=uiBufferSize,
						udiCount:=1);
		// copy the endian buffer into buffer
		SysMemCpy(pDest:=_pbWriteBuffer, pSrc:=ADR(_abEndianBuffer), udiCount:=uiBufferSize);
	ELSE
		// copy the actual data into buffer
		SysMemCpy(pDest:=_pbWriteBuffer, pSrc:=pbBuffer, udiCount:=uiBufferSize);
	END_IF

	// ship it
	THIS^._bWriteSuccess := FALSE;
	// use default or specified Id 
	THIS^._sHeader := psId^;
END_IF
-------------------------------------------------------------------------------
_bBuildGetCpu
-------------------------------------------------------------------------------

                  

Purpose: Build the stReadCpuReq packet

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _bBuildGetCpu: BOOL

VAR_INPUT
    eInfoType: eSystemStateList;
    pstReadSzlReq: pointer;
END_VAR

VAR_OUTPUT
    uiSize: UINT;
END_VAR
pstReadSzlReq^.tpkt.length := SIZEOF(pstReadSzlReq^);
pstReadSzlReq^.commHeader.protocolId := 16#32;
pstReadSzlReq^.commHeader.ROSCTR := eROSCTR.userData;
pstReadSzlReq^.commHeader.paramLen := SIZEOF(stReadSzlReq.userDataParam);
pstReadSzlReq^.commHeader.dataLen := 16#08; // data section of stReadSzlReq
pstReadSzlReq^.userDataParam.methodType := eParamMethod.request;
pstReadSzlReq^.userDataParam.functionGroup := eFunctionGroup.cpuFunctionsReq;
pstReadSzlReq^.userDataParam.subfunction := eCpuSubfunction.readSZL;
pstReadSzlReq^.szlId := eInfoType;
pstReadSzlReq^.dataLen := 16#04; // set since we share structs
// le sighs, Siemens is big endian
SysMemForceSwap(pbyBuffer:=ADR(pstReadSzlReq^.tpkt.length), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(pstReadSzlReq^.commHeader.redId), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(pstReadSzlReq^.commHeader.PDUR), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(pstReadSzlReq^.commHeader.paramLen), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(pstReadSzlReq^.commHeader.dataLen), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(pstReadSzlReq^.dataLen), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(pstReadSzlReq^.szlId), udiSize:=2, udiCount:=1);
SysMemForceSwap(pbyBuffer:=ADR(pstReadSzlReq^.szlIndex), udiSize:=2, udiCount:=1);
uiSize := SIZEOF(stReadSzlReq);
_bBuildGetCpu := TRUE;
-------------------------------------------------------------------------------
_bParseGetCpu
-------------------------------------------------------------------------------

                  

Purpose: Parse CPU module info and module status

* Author: NothinRandom
* v1.0	December 10, 2021
* Note:




METHOD _bParseGetCpu: BOOL

VAR_INPUT
    pbTcpReadBuffer: pointer;
    uiTcpReadSize: UINT;
    eInfoType: eSystemStateList;
    pstCpuInfo: pointer;
END_VAR

VAR
    _stReadSzlRes: stReadSzlRes;
    _uiDataOffset: UINT;
    _uiParamLen: UINT;
    _uiSections: UINT;
    _uiIndex: UINT;
    _pbDataOffset: pointer;
    _psInfo: pointer;
    _pbTemp: pointer;
    _uiSzlIndex: UINT;
END_VAR
// copy to get high level details
SysMemCpy(pDest:=ADR(_stReadSzlRes), pSrc:=pbTcpReadBuffer, udiCount:=SIZEOF(_stReadSzlRes));

// checking
IF (_stReadSzlRes.commHeader.ROSCTR <> eROSCTR.ackData AND 
	_stReadSzlRes.userDataParam.methodType <> eParamMethod.response AND 
	_stReadSzlRes.userDataParam.functionGroup <> eFunctionGroup.cpuFunctionsRes) THEN
	RETURN;
END_IF

// calculate offset of tcp read buffer where data starts 
_uiDataOffset := SIZEOF(_stReadSzlRes);
_pbDataOffset := pbTcpReadBuffer + _uiDataOffset;

_uiParamLen := uiSwap(pbInput:=ADR(_stReadSzlRes.szlPartialLen));
_uiSections := uiSwap(pbInput:=ADR(_stReadSzlRes.szlPartialCount));

CASE eInfoType OF
	eSystemStateList.cpuId:
		FOR _uiIndex := 1 TO _uiSections BY 1 DO
			_psInfo := _pbDataOffset + 2; // info after index
			_uiSzlIndex := uiSwap(pbInput:=_pbDataOffset);
			CASE _uiSzlIndex OF
				1: // name of automation system:
					pstCpuInfo^.sSystemName := _psInfo^;
				2: // name of the module
					pstCpuInfo^.sModuleName := _psInfo^;
				3: // plant designation of the modules
					pstCpuInfo^.uiPlantId := uiSwap(pbInput:=_psInfo);
					pstCpuInfo^.sPlantId := _xByteToHexStr(xiInput:=pstCpuInfo^.uiPlantId, bPrefix:=THIS^._bHexPrefix);
				4: // copyright entry
					pstCpuInfo^.sCopyright :=_psInfo^;
				5: // serial number of the module
					pstCpuInfo^.sSerialNumber :=_psInfo^;
				7: // module name type
					pstCpuInfo^.sCpuType :=_psInfo^;
				8: // serial number of the memory card
					pstCpuInfo^.sMcSerialNumber :=_psInfo^;
				9: // manufacter and profile of a cpu module
					pstCpuInfo^.uiManufacturerId := uiSwap(pbInput:=_psInfo, pbOutput=>_pbTemp);
					pstCpuInfo^.sManufacturerId := _xByteToHexStr(xiInput:=pstCpuInfo^.uiManufacturerId, bPrefix:=THIS^._bHexPrefix);
					pstCpuInfo^.uiProfileId := uiSwap(pbInput:=_pbTemp, pbOutput=>_pbTemp);
					pstCpuInfo^.sProfileId := _xByteToHexStr(xiInput:=pstCpuInfo^.uiProfileId, bPrefix:=THIS^._bHexPrefix);
					pstCpuInfo^.uiProfileSpec := uiSwap(pbInput:=_pbTemp, pbOutput=>_pbTemp);
					pstCpuInfo^.sProfileSpec := _xByteToHexStr(xiInput:=pstCpuInfo^.uiProfileSpec, bPrefix:=THIS^._bHexPrefix);
				10: // OEM id of a module
					pstCpuInfo^.sOemCopyright := _psInfo^;
					_pbTemp := _psInfo + 26;
					pstCpuInfo^.uiOemId := uiSwap(pbInput:=_pbTemp, pbOutput=>_pbTemp);
					pstCpuInfo^.sOemId := _xByteToHexStr(xiInput:=pstCpuInfo^.uiOemId, bPrefix:=THIS^._bHexPrefix);
					pstCpuInfo^.udiOemAddId := udiSwap(pbInput:=_pbTemp);
					pstCpuInfo^.sOemAddId := _xByteToHexStr(xiInput:=pstCpuInfo^.udiOemAddId, bPrefix:=THIS^._bHexPrefix);
				11: // location id of a module
					pstCpuInfo^.sLocationId := _psInfo^;
			END_CASE
			_pbDataOffset := _pbDataOffset + _uiParamLen;
		END_FOR
	eSystemStateList.cpuState:
		_pbDataOffset := _pbDataOffset + 3;
		pstCpuInfo^.bRequestedMode := bGetLowNibble(pbInput:=_pbDataOffset);
		pstCpuInfo^.sRequestedMode := _sGetCpuState(bState:=pstCpuInfo^.bRequestedMode);
		pstCpuInfo^.bPreviousMode := bGetHighNibble(pbInput:=_pbDataOffset);
		pstCpuInfo^.sPreviousMode := _sGetCpuState(bState:=pstCpuInfo^.bPreviousMode);
END_CASE

_bParseGetCpu := TRUE;
-------------------------------------------------------------------------------
bGetCpuState
-------------------------------------------------------------------------------

                  

Purpose: Get CPU module status

* Author: NothinRandom
* v1.0	December 20, 2021
* Note:




METHOD bGetCpuState: BOOL
// call main if we are initialized,
// else stack overflow
IF (THIS^._bInitialized AND THIS^._uiInitialize <> 0) THEN
	THIS^();
END_IF

// requires TCP active
IF ((NOT THIS^._bTcpConnected) OR THIS^._bError) THEN
	RETURN;
END_IF

// write only when available
IF (THIS^._uiCOTP = eCOTP.nop) THEN
	THIS^._uiCOTP := eCOTP.data;
	THIS^._uiROSCTR := eROSCTR.userData;
	THIS^._uiFunctionGroup := eFunctionGroup.cpuFunctionsReq;
	THIS^._uiSubfunction := eCpuSubfunction.readSZL;
	THIS^._uiSZL := eSystemStateList.cpuState;
	bGetCpuState := TRUE;
END_IF
-------------------------------------------------------------------------------
_sGetCpuState
-------------------------------------------------------------------------------

                  

Purpose: Return CPU operating state

* Author: NothinRandom
* v1.0	December 20, 2021
* Note:




METHOD _sGetCpuState: string

VAR_INPUT
    bState: BYTE;
END_VAR
CASE bState OF
	eCpuState.stop:
		_sGetCpuState := 'Stop';
	eCpuState.run:
		_sGetCpuState := 'Run';
	ELSE
		_sGetCpuState := 'Unknown';
END_CASE
-------------------------------------------------------------------------------
_sGetDayOfWeek
-------------------------------------------------------------------------------

                  

Purpose: Return day of the week as ASCII

* Author: NothinRandom
* v1.0	December 20, 2021
* Note:




METHOD _sGetDayOfWeek: string

VAR_INPUT
    eDOW: eDayOfWeek;
END_VAR
CASE eDOW OF 
	eDayOfWeek.sunday:
		_sGetDayOfWeek := 'Sunday';
	eDayOfWeek.monday:
		_sGetDayOfWeek := 'Monday';
	eDayOfWeek.tuesday:
		_sGetDayOfWeek := 'Tuesday';
	eDayOfWeek.wednesday:
		_sGetDayOfWeek := 'Wednesday';
	eDayOfWeek.thursday:
		_sGetDayOfWeek := 'Thursday';
	eDayOfWeek.friday:
		_sGetDayOfWeek := 'Friday';
	eDayOfWeek.saturday:
		_sGetDayOfWeek := 'Saturday';
	ELSE
		_sGetDayOfWeek := 'Unknown';
END_CASE
-------------------------------------------------------------------------------
Timestamp
-------------------------------------------------------------------------------

            

Purpose: Create timestamp object since 1970

* Author: NothinRandom
* v1.0 June 24, 2017
* Note:
	* Install: SysTime




FUNCTION_BLOCK Timestamp

VAR
    uliUTC: SYSTIME;
    uliUs: ULINT;
    uliNs: ULINT;
END_VAR

-------------------------------------------------------------------------------
Now
-------------------------------------------------------------------------------

                  

Purpose: Get Epoch time in milliseconds since 1970

* Author: NothinRandom
* v1.0 June 24, 2017
* Note:
	* error if value <> 0




METHOD Now: UDINT
// milliseconds is the highest accurate resolution
Now:= SysTimeRtcHighResGet(pTimestamp:=uliUTC); //
SysTimeGetUs(pUsTime:=uliUs); // get microseconds
SysTimeGetNs(pUsTime:=uliNs); // get nanoseconds
-------------------------------------------------------------------------------
TimeF
-------------------------------------------------------------------------------

                  

Purpose: Get timestamp in custom string format

* Author: NothinRandom
* v1.0 June 24, 2017
* Note:
	* ISO8601: 2017-06-26T10:30:00.453Z ('#A-#D-#HT#N:#R:#T.#VZ')
	* Readable: June 26, 2017 - 10:30:00AM ('#F #G, #A' - #O:#R#T#L')




METHOD TimeF: string

VAR_INPUT
    Format: string;
END_VAR

VAR
    sdtUTC: SYSTIMEDATE;
    dtUTC: DT;
END_VAR
SysTimeRtcConvertHighResToDate(pTimestamp:= uliUTC, pDate:= sdtUTC); // convert SYSTIME to SYSTIMEDATE
dtUTC := OSCAT_BASIC.SET_DT( // convert SYSTIMEDATE to DATE_AND_TIME variable dt#yyyy-mm-dd-hh:mm:ss
	year:=UINT_TO_INT(sdtUTC.wYear),
	month:=UINT_TO_INT(sdtUTC.wMonth),
	day:=UINT_TO_INT(sdtUTC.wDay),
	hour:=UINT_TO_INT(sdtUTC.wHour),
	minute:=UINT_TO_INT(sdtUTC.wMinute),
	second:=UINT_TO_INT(sdtUTC.wSecond));
TimeF := OSCAT_BASIC.DT_TO_STRF(DTI:=dtUTC, MS:=UINT_TO_INT(sdtUTC.wMilliseconds), FMT:=Format, LANG:=0); // format string
-------------------------------------------------------------------------------
FB_init
-------------------------------------------------------------------------------


METHOD FB_init: BOOL

VAR_INPUT
    bInitRetains: BOOL;
    bInCopyCode: BOOL;
END_VAR
This^.Now(); // instantiate object with snap of time
-------------------------------------------------------------------------------
getLocalTime
-------------------------------------------------------------------------------

                  

Purpose: Get local time

* Author: NothinRandom
* v1.0 June 24, 2017
* Note:
	* Look at TimeF for format




METHOD getLocalTime: string

VAR_INPUT
    sFormat: string;
    xDST: BOOL;
    iTimeZoneOffset: INT;
END_VAR

VAR
    sdtLocal: SYSTIMEDATE;
    dtLocal: DT;
END_VAR
(*
SysTimeRtcConvertHighResToDate(pTimestamp:= uliUTC, pDate:= sdtLocal); // convert SYSTIME to SYSTIMEDATE
dtLocal:= OSCAT_BASIC.SET_DT( // convert SYSTIMEDATE to DATE_AND_TIME variable dt#yyyy-mm-dd-hh:mm:ss
	year:= UINT_TO_INT(sdtLocal.wYear),
	month:= UINT_TO_INT(sdtLocal.wMonth),
	day:= UINT_TO_INT(sdtLocal.wDay),
	hour:= UINT_TO_INT(sdtLocal.wHour),
	minute:= UINT_TO_INT(sdtLocal.wMinute),
	second:= UINT_TO_INT(sdtLocal.wSecond));
dtLocal:= OSCAT_BASIC.UTC_TO_LTIME(UTC:= dtLocal, DST_ENABLE:= xDST, TIME_ZONE_OFFSET:= iTimeZoneOffset); // convert UTC to local time
LocalTime:= OSCAT_BASIC.DT_TO_STRF(dtLocal, 0, sFormat, 0); // format string
*)

//getLocalTime := TO_STRING(SysTimeRtcConvertHighResToLocal(pTimestamp:=uliUTC, pDate:= sdtLocal));

VAR_INPUT
    pwFrom: pointer;
    pwTo: pointer;
    udiBufferSize: UDINT;
END_VAR

VAR
    _pwFrom: pointer;
    _pwTo: pointer;
    _diFromLen: DINT;
    _diToLen: DINT;
    _diIndex: DINT;
END_VAR
IF (pwFrom = 0 OR pwTo = 0) THEN
	RETURN; // return FALSE
END_IF

_diFromLen := StrLenW(pwFrom);
_diToLen := StrLenW(pwTo);

IF ((_diToLen*2 + _diFromLen*2 + 2) > UDINT_TO_DINT(udiBufferSize)) THEN
	RETURN; // return FALSE => result buffer not big enough
END_IF

_pwFrom:= pwFrom;
_pwTo:= pwTo;

// Concatenate the string pwFrom to pwTo
FOR _diIndex:= 0 TO _diFromLen - 1 DO
	_pwTo[_diToLen + _diIndex] := _pwFrom[_diIndex];
END_FOR
// Set the string termination
_pwTo[_diToLen + _diIndex] := 0;
bWStringConcat:= TRUE;

VAR_INPUT
    pwInput: pointer;
    diPos: DINT;
    pwOutput: pointer;
    udiSize: UDINT;
END_VAR

VAR
    _diIndex: DINT;
    _diEnd: DINT;
END_VAR
_diEnd := MIN(diPos + StrLenA(pstData:=pwInput), UDINT_TO_DINT(udiSize));
IF _diEnd > 0 THEN 
	_diEnd := _diEnd -1; 
END_IF;
FOR _diIndex := diPos TO _diEnd DO
	pwOutput^ := pwInput^;
	pwInput := pwInput + 1;
	pwOutput := pwOutput + 1;
END_FOR;

diWStringToBuffer := _diIndex;

VAR_INPUT
    pwInput: pointer;
    diStart: DINT;
    diStop: DINT;
    pwOutput: pointer;
    uiOutSize: UINT;
END_VAR

VAR
    _diInputLen: DINT;
    _uiIndex: DINT;
    _uiStop: DINT;
    _uiStart: DINT;
END_VAR
// get length of input wstring
_diInputLen := StrLenW(pstData:= pwInput);

IF (_diInputLen = 0) THEN
	RETURN;
END_IF;

_uiStart := MIN(diStart, _diInputLen -1);
_uiStop := MIN(diStop, _diInputLen -1);

// check for maximum string_length
IF (_uiStop - _uiStart + 1) >= uiOutSize THEN
	_uiStop := _uiStart + uiOutSize - 1;
END_IF;

FOR _uiIndex := _uiStart TO _uiStop DO
	pwOutput^:= pwInput^;
	pwOutput:= pwOutput + 1;
END_FOR;

pwOutput^ := 0; // terminate the string

VAR_INPUT
    pbFrom: pointer;
    pbTo: pointer;
    udiBufferSize: UDINT;
END_VAR

VAR
    _diFromLen: DINT;
    _diToLen: DINT;
    _diIndex: DINT;
END_VAR
// null pointer
IF (pbFrom = 0 OR pbTo = 0) THEN
	RETURN;
END_IF

_diFromLen:= StrLenA(pstData:=pbFrom);
_diToLen:= StrLenA(pstData:=pbTo);

// result buffer not big enough
IF ((_diToLen + _diFromLen + 1) > UDINT_TO_DINT(udiBufferSize)) THEN
	RETURN;
END_IF

// Concatenate pbFrom to pbTo
FOR _diIndex := 0 TO _diFromLen - 1 DO
	pbTo[_diToLen+_diIndex] := pbFrom[_diIndex];
END_FOR

// Set the string termination
pbTo[_diToLen + _diIndex] := 0;

bStringConcat:= TRUE;

VAR_INPUT
    pbInput: pointer;
    diPos: DINT;
    pbOutput: pointer;
    udiBufferSize: UDINT;
END_VAR

VAR
    _diIndex: DINT;
    _diEnd: DINT;
END_VAR
_diEnd := MIN(diPos + StrLenA(pstData:=pbInput), UDINT_TO_DINT(udiBufferSize));
IF (_diEnd > 0) THEN 
	_diEnd := _diEnd - 1; 
END_IF;
FOR _diIndex := diPos TO _diEnd DO
	pbOutput^ := pbInput^;
	pbInput := pbInput + 1;
	pbOutput := pbOutput + 1;
END_FOR;

// terminate string
pbOutput^ := 0;

// output index is length of string plus offset
diStringToBuffer := _diIndex + diPos;

VAR_INPUT
    psInput: pointer;
    diStart: DINT;
    diStop: DINT;
    pbOutput: pointer;
    uiSize: UINT;
END_VAR

VAR
    _uiInputLen: UINT;
    _uiIndex: DINT;
    _uiStop: DINT;
    _uiStart: DINT;
END_VAR
// get length of string
_uiInputLen := TO_UINT(StrLenA(pstData:=psInput));

// simple checks
IF (_uiInputLen = 0) THEN
	RETURN;
END_IF;

_uiStart := MIN(diStart, _uiInputLen -1);
_uiStop := MIN(diStop, _uiInputLen -1);

// check for maximum string_length and set stop index
IF ((_uiStop - _uiStart + 1) >= uiSize) THEN
	_uiStop := _uiStart + uiSize - 1;
END_IF;

FOR _uiIndex := _uiStart TO _uiStop DO
	pbOutput^ := psInput^[_uiIndex];
	pbOutput := pbOutput + 1;
END_FOR;

pbOutput^ := 0; // terminate the string
sBufferToString := TRUE;

VAR_INPUT
    pbCurrentValue: pointer;
    pbNewValue: pointer;
END_VAR

VAR_INOUT
    pbInput: pointer;
    uiInputSize: UINT;

END_VAR

VAR
    _iCurrentValueLength: INT;
    _iNewValueLength: INT;
    _iInputLength: INT;
    _diPosition: DINT;
END_VAR
// get length of strings
_iCurrentValueLength := DINT_TO_INT(StrLenA(pstData:=pbCurrentValue));
_iNewValueLength := DINT_TO_INT(StrLenA(pstData:=pbNewValue));
_iInputLength := DINT_TO_INT(StrLenA(pstData:=pbInput));

_diPosition:= StrFindA(pst1:=pbInput, pst2:=pbCurrentValue, uiSearchStart:=1);
WHILE (_diPosition > 0) DO
	StrReplaceA(pstInput:= pbInput,
				uiInputBufferSize:=uiInputSize,
				pstReplaceWith:=pbNewValue,
				iLengthInput:=_iInputLength,
				iLengthToReplace:=_iCurrentValueLength,
				iLengthToReplaceWith:=_iNewValueLength,
				iPosition:= DINT_TO_INT(_diPosition));
	_diPosition:= StrFindA(pst1:=pbInput, pst2:=pbNewValue, uiSearchStart:=DINT_TO_UINT(_diPosition));
END_WHILE;

bStringReplaceAll := TRUE;

VAR_INPUT
    pbInput: pointer;
    sChar: string;
END_VAR

VAR
    _diPosition: DINT;
    _uiOffset: UINT;
END_VAR
// empty string
IF (pbInput^ = 0) THEN
	RETURN;
END_IF

WHILE (_diPosition > 0) DO // find other instances
	_diPosition := StrFindA(pst1:=pbInput, pst2:=ADR(sChar), uiSearchStart:=(DINT_TO_UINT(_diPosition)+_uiOffset)); // find the instance
	if(_diPosition > 0) then // continue searching
		_uiOffset := 1;
	END_IF
	iStringCountChar := iStringCountChar + 1; // return minimum of 1
END_WHILE;

VAR_INPUT
    psInput: pointer;
    sChar: string;
    iIndex: INT;
    pbOutput: pointer;
    uiSize: UINT;
END_VAR

VAR
    _diInputLen: DINT;
    _diStart: DINT;
    _diStop: DINT;
    _iCounter: INT;
END_VAR
_diInputLen := StrLenA(pstData:=psInput);

WHILE (_diStop > 0) DO
	_diStop:= StrFindA(pst1:=psInput, pst2:=ADR(sChar), uiSearchStart:=DINT_TO_UINT(_diStart) + 1); // find instance
	IF (_diStop = 0) THEN // get ending
		sBufferToString(psInput:= psInput, diStart:=_diStart, diStop:=_diInputLen, pbOutput:=pbOutput, uiSize:=uiSize); // parse substring
		RETURN;
	ELSIF(_iCounter = iIndex) THEN // matching index
		sBufferToString(psInput:=psInput, diStart:=_diStart, diStop:=(_diStop-2), pbOutput:=pbOutput, uiSize:=uiSize); // -2 for index difference
		RETURN;
	END_IF
	_diStart:= _diStop; // update head
	_iCounter:= _iCounter + 1;
END_WHILE;

VAR_INPUT
    psInput: pointer;
END_VAR

VAR
    _diInputLen: DINT;
    _diIndex: DINT;
END_VAR
_diInputLen := StrLenA(pstData:=psInput);

// empty string
IF (_diInputLen = 0) THEN
	RETURN;
END_IF

WHILE (_diIndex < _diInputLen) DO
	IF ((psInput^[_diIndex] <> 32) AND (psInput^[_diIndex] < 48 OR psInput^[_diIndex] > 57)) THEN
		RETURN;
	END_IF
	_diIndex := _diIndex + 1;
END_WHILE

bStringIsDigit := TRUE;

VAR_INPUT
    xiInput: __SYSTEM.AnyType;
    bPrefix: BOOL;
END_VAR

VAR
    _uiIndex: UINT;
    _uiSize: UINT;
    _bTemp: BYTE;
    _pbOut: pointer;
END_VAR
// get size of input data type
_uiSize := TO_UINT(xiInput.diSize);

// get output adress pointer 
_pbOut := ADR(_xByteToHexStr) + _uiSize*2;

// add '0x'
IF (bPrefix) THEN
	_xByteToHexStr := '0x';
	_pbOut := _pbOut + 2;
END_IF

// terminate string
_pbOut^:= 0;

// write hex characters in little endian
FOR _uiIndex := 1 TO _uiSize*2 DO;
	// decrement the pointer
	_pbOut := _pbOut - 1;
	// read low 4b or high 4b
	IF ((_uiIndex MOD 2) = 1) THEN
		_bTemp := xiInput.pValue^ AND 16#0F;
	ELSE
		_bTemp := SHR(xiInput.pValue^, 4) AND 16#0F;
		// increment to next pointer byte
		xiInput.pValue := xiInput.pValue + 1;
	END_IF
	// convert to hex character
	IF (_bTemp <= 9) THEN
		_bTemp := _bTemp + 48; 
	ELSE 
		_bTemp := _bTemp + 55; 
	END_IF;
	// write to output
	_pbOut^ := _bTemp;
END_FOR;

VAR
    udiStructSize: UDINT;
    SockAdapterInfo: NBS.SOCK_ADAPTER_INFORMATION;
    hAdapter: pointer;
    udiResult: UDINT;
    DefaultGateway: NBS.INADDR;
END_VAR
udiStructSize := SIZEOF(sAdapterInfo);
hAdapter := NBS.SysSockGetFirstAdapterInfo(ADR(sAdapterInfo), ADR(udiStructSize), ADR(udiResult));
IF SockAdapterInfo.DefaultGateway.ulAddr <> 0 AND SockAdapterInfo.DefaultGateway.ulAddr <> 16#FFFFFFFFF THEN
   DefaultGateway:= SockAdapterInfo.DefaultGateway;	   
ELSE
   WHILE udiResult = 0 DO
	  hAdapter:= NBS.SysSockGetNextAdapterInfo(hAdapter, ADR(sAdapterInfo), ADR(udiStructSize), ADR(udiResult));
	  IF SockAdapterInfo.DefaultGateway.ulAddr <> 0 AND SockAdapterInfo.DefaultGateway.ulAddr <> 16#FFFFFFFFF THEN
		 DefaultGateway:= SockAdapterInfo.DefaultGateway;
	  RETURN;
	  END_IF
   END_WHILE
END_IF

VAR_INPUT
    sIPv4: string;
END_VAR
bIsIPv4:= (iStringCountChar(pbInput:= ADR(sIPv4), sChar:= '.') = 4);

VAR_INPUT
    dwIp: DWORD;
END_VAR

VAR
    _pbIp: pointer;
    _uiIndex: UINT;
    _sOctet: string;
END_VAR
IF (dwIp = 0) THEN
	sIPv4 := '0.0.0.0';
	RETURN;
END_IF

// get pointer to IP address
_pbIp := ADR(dwIp) + 3;

FOR _uiIndex := 0 TO 3 DO
	_sOctet := TO_STRING(_pbIp^);
	bStringConcat(pbFrom:=ADR(_sOctet), pbTo:=ADR(sIPv4), udiBufferSize:=SIZEOF(sIPv4));
	// add '.'
	IF (_uiIndex < 3) THEN
		bStringConcat(pbFrom:=ADR('.'), pbTo:=ADR(sIPv4), udiBufferSize:=SIZEOF(sIPv4));
	END_IF
	_pbIp := _pbIp - 1; // update pointer index
END_FOR

VAR_INPUT
    pbInput: pointer;
END_VAR

VAR_OUTPUT
    pbOutput: pointer;
END_VAR
bDecToBcd := SHL(pbInput^ / 10, 4) + (pbInput^ MOD 16#0A);
pbOutput := pbInput + SIZEOF(bDecToBcd);

VAR_INPUT
    pbInput: pointer;
END_VAR

VAR_OUTPUT
    pbOutput: pointer;
END_VAR
bBcdToDec := SHR(pbInput^, 4) * 10 + (pbInput^ AND 16#0F);
pbOutput := pbInput + SIZEOF(bBcdToDec);

VAR_INPUT
    pbInput: pointer;
END_VAR

VAR_OUTPUT
    pbOutput: pointer;
END_VAR

VAR
    _puiTemp: pointer;
END_VAR
_puiTemp := pbInput;	// copy reference
uiSwap := _puiTemp^;	// deference
// change endian and update output pointer
SysMemForceSwap(pbyBuffer:=ADR(uiSwap), udiSize:=SIZEOF(uiSwap), udiCount:=1);
pbOutput := pbInput + SIZEOF(uiSwap);

VAR_INPUT
    pbInput: pointer;
END_VAR

VAR_OUTPUT
    pbOutput: pointer;
END_VAR

VAR
    _pudiTemp: pointer;
END_VAR
_pudiTemp := pbInput;	// copy reference
udiSwap := _pudiTemp^;	// deference
// change endian and update output pointer
SysMemForceSwap(pbyBuffer:=ADR(udiSwap), udiSize:=SIZEOF(udiSwap), udiCount:=1);
pbOutput := pbInput + SIZEOF(udiSwap);

VAR_INPUT
    pbInput: pointer;
END_VAR

VAR_OUTPUT
    pbOutput: pointer;
END_VAR

VAR
    _pudiTemp: pointer;
END_VAR
_pudiTemp := pbInput;	// copy reference
uliSwap := _pudiTemp^;	// deference
// change endian and update output pointer
SysMemForceSwap(pbyBuffer:=ADR(uliSwap), udiSize:=SIZEOF(uliSwap), udiCount:=1);
pbOutput := pbInput + SIZEOF(uliSwap);

VAR_INPUT
    pbInput: pointer;
END_VAR

VAR_OUTPUT
    pbOutput: pointer;
END_VAR
bGetHighNibble := SHR(pbInput^, 4) AND 16#0F;
pbOutput := pbInput + SIZEOF(bGetHighNibble);

VAR_INPUT
    pbInput: pointer;
END_VAR

VAR_OUTPUT
    pbOutput: pointer;
END_VAR
bGetLowNibble := pbInput^ AND 16#0F;
pbOutput := pbInput + SIZEOF(bGetLowNibble);

VAR_INPUT
    eType: eFromLDT;
    ldtInput: LDATE_AND_TIME;
END_VAR

VAR
    _lwDateTime: LWORD;
END_VAR

VAR
    _lwOneDay: LWORD;
    _lwOneHour: LWORD;
    _lwOneMinute: LWORD;
    _lwOneSecond: LWORD;
END_VAR
_lwDateTime := TO_LWORD(ldtInput);

CASE eType OF
	eFromLDT.year:
		uiFromLDT := TO_UINT((_lwDateTime/_lwOneSecond + 43200) / 31557600 + 1970);
	eFromLDT.month:
		uiFromLDT := uiMonthOfDate(ldtInput:=ldtInput);
	eFromLDT.day:
		uiFromLDT := uiDayOfMonth(ldtInput:=ldtInput);
	eFromLDT.hour:
		uiFromLDT := TO_UINT((_lwDateTime MOD (_lwOneDay))/(_lwOneHour));
	eFromLDT.minute:
		uiFromLDT := TO_UINT((_lwDateTime MOD (_lwOneHour))/(_lwOneMinute));
	eFromLDT.second:
		uiFromLDT := TO_UINT((_lwDateTime MOD (_lwOneMinute))/(_lwOneSecond));
	eFromLDT.millisecond:
		uiFromLDT := TO_UINT((_lwDateTime MOD (_lwOneSecond))/(1000*1000));
	eFromLDT.microsecond:
		uiFromLDT := TO_UINT((_lwDateTime MOD (1000*1000))/1000);
	eFromLDT.nanosecond:
		uiFromLDT := TO_UINT(_lwDateTime MOD 1000);
	eFromLDT.dayOfYear:
		uiFromLDT := uiDayOfYear(ldtInput:=ldtInput);
	eFromLDT.dayOfWeek: // look at eDayOfWeek
		uiFromLDT := TO_UINT((_lwDateTime/(_lwOneSecond*86400) + 4) MOD 7) + 1;
END_CASE


VAR_INPUT
    year: UINT;
    month: UINT;
    day: UINT;
END_VAR

VAR
    ofs: ARRAY [..] OF ;
    ENDIF: BOOL;
END_VAR
IF (month > 2) AND (SHL(YEAR,14) = 0) THEN (* we add one day for leap year *)
	dwSetDate := (TO_DWORD(ofs[MONTH] + day ) + SHR(TO_DWORD(year) * 1461 - 2878169, 2))*86400;
ELSE
	dwSetDate := (TO_DWORD(ofs[MONTH] + day - 1) + SHR(TO_DWORD(year) * 1461 - 2878169, 2))*86400;
END_IF;

(* old code
IF month > 2 THEN
	count := (month - 1) * 30;
	IF month > 7 THEN count := count + SHR(month - 3,1); ELSE count := count + SHR(month - 4,1); END_IF;
	(* chech for leap year and add one day if true *)
	IF SHL(year,14) = 0 THEN count := count + 1; END_IF;
ELSE
	count := (month - 1) * 31;
END_IF;

SET_DATE := DWORD_TO_DATE((INT_TO_DWORD(count + day - 1) + SHR(INT_TO_DWORD(year) * 1461 - 2878169, 2)) * 86400);
*)

(* revision history
hm	4. aug. 2006	rev 1.0
	original version

hm	19 sep. 2007	rev 1.1
	use function leap_year to calculate leap year, more exceptions are handled

hm	1. okt	2007	rev 1.2
	added compatibility to step7

hm	16.dec 2007		rev 1.3
	changed code to improove performance

hm	3. jan. 2008	rev 1.4
	further improvements in performance

hm	16. mar. 2008	rev 1.5
	added type conversions to avoid warnings under codesys 3.0

hm	7. apr. 2008	rev 1.6
	deleted unused step7 code

hm	14. oct. 2008	rev 1.7
	optimized code for better performance

hm	25. oct. 2008	rev 2.0
	new code using setup constants

hm	16. nov. 2008	rev 2.1
	added typecasts to avoid warnings

hm	22. jan. 2011	rev 2.2
	improved performance

hm	29. dec. 2011	rev 2.3
	improved performance

hm 3. FEB 2021 rev 2.4
	new code to be more readable
*)

VAR_INPUT
    uiYear: UINT;
    uiMonth: UINT;
    uiDay: UINT;
    uliHour: ULINT;
    uliMinute: ULINT;
    uliSsecond: ULINT;
    uliMSecond: ULINT;
    uliUSecond: ULINT;
    uliNSecond: ULINT;
END_VAR
ldtCreateFromParam := TO_LDT((TO_LWORD(dwSetDate(uiYear, uiMonth, uiDay))
									+ uliHour * 3600
									+ uliMinute * 60
									+ uliSsecond) * 1000 * 1000 * 1000
						+ uliMSecond * 1000 * 1000
						+ uliUSecond * 1000
						+ uliNSecond);

VAR_INPUT
    ldtInput: LDATE_AND_TIME;
END_VAR
uiDayOfYear := TO_UINT((TO_LWORD(ldtInput)/(24*60*60*1000*1000*1000)) MOD UDINT#1461);
IF uiDayOfYear > 729 THEN
	IF uiDayOfYear > 1095 THEN
		uiDayOfYear := uiDayOfYear - 1095;
	ELSE
		uiDayOfYear := uiDayOfYear - 729;
	END_IF;
ELSIF uiDayOfYear > 364 THEN
	uiDayOfYear := uiDayOfYear - 364;
ELSE
	uiDayOfYear := uiDayOfYear + 1;
END_IF;


VAR_INPUT
    ldtInput: LDATE_AND_TIME;
END_VAR
uiMonthOfDate := uiDayOfYear(ldtInput);
IF uiMonthOfDate < 32 THEN
	uiMonthOfDate := 1;
ELSIF bLeapOfDate(ldtInput) THEN
	uiMonthOfDate := (uiMonthOfDate * 53 + 1668) / 1623;
ELSE
	uiMonthOfDate := (uiMonthOfDate * 53 + 1700) / 1620;
END_IF;

VAR_INPUT
    ldtInput: LDATE_AND_TIME;
END_VAR
bLeapOfDate := SHL(((TO_LWORD(ldtInput)/(1000*1000*1000) + 43200) / 31557600), 30) = 16#80000000;

VAR_INPUT
    ldtInput: LDATE_AND_TIME;
END_VAR

VAR
    _auiMonthOffset: ARRAY [..] OF ;
    _uiLeap: UINT;
END_VAR
(* calculate the day in the year *)
uiDayOfMonth := uiDayOfYear(ldtInput);
(* leap will be set to one for a leap year *)
_uiLeap := BOOL_TO_UINT(bLeapOfDate(ldtInput));
(* if leap year deduct one from the days of the year *)
uiDayOfMonth := uiDayOfMonth - _uiLeap;
(* search if we are in month december to march ? *)
IF uiDayOfMonth > _auiMonthOffset[9] THEN
	IF uiDayOfMonth > _auiMonthOffset[11] THEN
		IF uiDayOfMonth > _auiMonthOffset[12] THEN
			uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[12];
		ELSE
			uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[11];
		END_IF;
	ELSE
		IF uiDayOfMonth > _auiMonthOffset[10] THEN
			uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[10];
		ELSE
			uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[9];
		END_IF;
	END_IF;
ELSIF uiDayOfMonth > _auiMonthOffset[5] THEN
	IF uiDayOfMonth > _auiMonthOffset[7] THEN
		IF uiDayOfMonth > _auiMonthOffset[8] THEN
			uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[8];
		ELSE
			uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[7];
		END_IF;
	ELSE
		IF uiDayOfMonth > _auiMonthOffset[6] THEN
			uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[6];
		ELSE
			uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[5];
		END_IF;
	END_IF;
ELSIF uiDayOfMonth > _auiMonthOffset[3] THEN
	IF uiDayOfMonth > _auiMonthOffset[4] THEN
		uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[4];
	ELSE
		uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[3];
	END_IF;
ELSE
	(* since now we must be in february or january we need to add leap again *)
	uiDayOfMonth := uiDayOfMonth + _uiLeap;
	IF uiDayOfMonth > _auiMonthOffset[2] THEN uiDayOfMonth := uiDayOfMonth - _auiMonthOffset[2]; END_IF;
	(* since nothing was true before, day_of_month must already be good *)
END_IF;

VAR
    mbs: CmpCrypto.CmpCrypto_Interfaces.RtsByteString;
    Result: CmpCrypto.CmpCrypto_Implementation.CmpCrypto_Interfaces.SysTypes.RTS_IEC_RESULT;
END_VAR
//Requires CmpCrypto library
//Requires device vendor to have included Crypto component in Runtime
Result := CmpCrypto.CryptoGenerateRandomNumber(ui32NumOfRandomBytes:= 4, pRandom:= ADR(mbs));
VAR_GLOBAL
    uiTcpBufferSize: UINT;
    bHexPrefix: BOOL;
    bRack: BYTE;
    bSlot: BYTE;
    udiTcpClientRetry: UDINT;
    udiTcpClientTimeout: UDINT;
    udiTcpWriteTimeout: UDINT;
    uiCommWriteTimeout: UINT;
END_VAR