Files
Codesys-Library-Atom-Modbus/compat/All_Source_Review.st
T
2026-09-30 21:17:41 -06:00

2297 lines
64 KiB
Smalltalk

(* ModbusMaster/atMbClientToString -- Interface *)
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE atMbClientToString :
(
noneOk := 0,
someOk := 1,
allOk := 2
);
END_TYPE
(* ModbusMaster/atMBExceptionCodes -- Interface *)
{attribute 'qualified_only'}
// {attribute 'strict'} NOT strict so can set with modbusFB.ExceptionCodes.
TYPE atMBExceptionCodes :
(
// Local / transport-level (non-Modbus) result
TIMEOUT := -1, // Communication timeout / no response
// Modbus exception codes (returned in exception response PDU)
RESPONSE_SUCCESS := 16#00, // Everything OK (no exception)
ILLEGAL_FUNCTION := 16#01, // Function code not supported by slave
ILLEGAL_DATA_ADDRESS := 16#02, // Data address not valid for slave (out of range / not mapped)
ILLEGAL_DATA_VALUE := 16#03, // Data value not valid (bad length/value/etc.)
SLAVE_DEVICE_FAILURE := 16#04, // Unrecoverable error while performing the requested action
ACKNOWLEDGE := 16#05, // Request accepted; processing started (use to avoid master timeout)
SLAVE_DEVICE_BUSY := 16#06, // Slave busy (e.g. long operation in progress); retry later
MEMORY_PARITY_ERROR := 16#08, // Memory parity error (special case; historically for FC 20/21)
GATEWAY_PATH_UNAVAILABLE := 16#0A, // Gateway problem: path unavailable / misconfigured / overloaded
GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND := 16#0B // Gateway problem: target device did not respond
);
END_TYPE
(* ModbusMaster/atMbClient -- Interface *)
{attribute 'monitoring_display' := 'toString'}
{attribute 'no_assign'}
FUNCTION_BLOCK ABSTRACT atMbClient
VAR_INPUT
{attribute 'symbol' := 'none'}
in_timeout: TIME := T#0MS; // If this is set, this will be used on requests, otherwise it will be calculated from in_dataRate.
{attribute 'symbol' := 'none'}
in_resetStats: BOOL; // Set this bool to true to reset statistics.
END_VAR
VAR_OUTPUT
{attribute 'symbol' := 'none'}
msgLastSecondCnt: UDINT; //Messages in the last second
{attribute 'symbol' := 'none'}
msgLastSecondTime: UDINT; //Total amount of time for all messages sent last second in ms
{attribute 'symbol' := 'none'}
numMsgSent: UDINT;
{attribute 'symbol' := 'none'}
numMsgReply: UDINT;
{attribute 'symbol' := 'none'}
allRequestsOnline: BOOL; //All requests are working
//{attribute 'symbol' := 'none'}
reqStats: ARRAY[0..99] OF POINTER TO atMbRequestStats; // For debugging modbus requests
END_VAR
VAR
{attribute 'symbol' := 'none'}
datarate: DINT := 0; //Bits per second data rate. Used to calculate reply timeout. Based on amount of time to transmit holding register read for max amount of registers. Default 154705 for Modbus TCP = 17ms to transmit 2650 bits
{attribute 'symbol' := 'none'}
client: REFERENCE TO ModbusFB.Client; // This is generated internally, no need to set.
{attribute 'symbol' := 'none'}
connectCnt: DINT; //Number of connection attempts
{attribute 'symbol' := 'none'}
initDone: BOOL;
{attribute 'symbol' := 'none'}
disconnectTimer: TON; //Connecting but not connected delay
{attribute 'symbol' := 'none'}
reconnectTimer: TON; //Not connecting and not connected delay
{attribute 'symbol' := 'none'}
msgCountLast: UDINT; //Last message count
{attribute 'symbol' := 'none'}
msgCountSum: UDINT; //Message count accumulator
{attribute 'symbol' := 'none'}
msgTimeSum: UDINT; //Amount of time for messsages accumulator
{attribute 'symbol' := 'none'}
pulse: atPulse; //Pulse generator
{attribute 'symbol' := 'none'}
requests: ARRAY[0..99] OF POINTER TO atMbReqBase; //list of request to direct
{attribute 'symbol' := 'none'}
requestCnt: UINT; //Number of active requests
{attribute 'symbol' := 'none'}
reqDoneSum: UDINT; //Count of done result on all requests
{attribute 'symbol' := 'none'}
reqErrSum: UDINT; //Count of error result on all requests
{attribute 'symbol' := 'none'}
i: DINT; //Loop variable
{attribute 'symbol' := 'none'}
nextIdx: DINT := -1; //Index of the active request
{attribute 'symbol' := 'none'}
priorityLowest: INT; //lowest request priority
{attribute 'symbol' := 'none'}
highestTimeSinceRun: UDINT; //lowest request priority
{attribute 'symbol' := 'none'}
allRequestsOfflineTON: TON; //All requests are offline delay
{attribute 'symbol' := 'none'}
requestNotExecutingTON: TON; //Delay after there are no requests execting
{attribute 'symbol' := 'none'}
recreateClientDly: TON; //Delay to make new client after force fail
{attribute 'symbol' := 'none'}
allRequestsOffline: BOOL; //All requests are offline
{attribute 'symbol' := 'none'}
connect: BOOL; // this is more of a "Request to connect, Requet to disconnect", will drop xConnect once all requests are done, wont start requests unless this is true.
{attribute 'symbol' := 'none'}
forceConnect: TON; //If after 5 seconds, all requests are still ofline, just force a reconnect.
test1: REAL;
test2: REAL;
destroyClient: BOOL;
recreateTest: BOOL;
END_VAR
VAR CONSTANT
{attribute 'symbol' := 'none'}
NEXT_REQUEST_DELAY: TIME := T#0MS; // Dont seem to need a delay
{attribute 'symbol' := 'none'}
RECONNECT_DELAY: TIME := T#500MS;
{attribute 'symbol' := 'none'}
DISCONNECT_DELAY: TIME := T#500MS;
{attribute 'symbol' := 'none'}
RECREATE_CLIENT_DELAY: TIME := T#1000MS;
{attribute 'symbol' := 'none'}
ALL_OFFLINE_DELAY: TIME := T#2S;
{attribute 'symbol' := 'none'}
FORCE_RECONNECT_DELAY: TIME := T#1S;
{attribute 'symbol' := 'none'}
PRIORITY_NONE: INT := 32767;
END_VAR
VAR
nativeMode : BOOL;
nativeHandle : UDINT;
nativeIP : DWORD;
nativePort : UINT;
nativeNowUs : SYSTIME;
nativeNowMs : ULINT;
nativeStatus : ARRAY[0..23] OF UDINT;
nativeActiveIndex : DINT := -1;
nativeSelected : POINTER TO atMbReqBase;
nativeResultReady : BOOL;
nativeFinishedIndex : DINT := -1;
nativeCallResult : UDINT;
nativeWindowStart : ULINT;
nativePrevConnected : BOOL;
nativeEnable : BOOL := TRUE;
nativeConnectTimeout : UDINT := 2000;
nativeRetryDelay : UDINT := 1000;
nativeHoldUncertainWrites : BOOL := FALSE;
END_VAR
VAR_INPUT
{attribute 'symbol' := 'none'}
nativeIO : ARRAY[0..1999] OF WORD;
{attribute 'symbol' := 'none'}
nativeActiveRequest : POINTER TO atMbReqBase;
{attribute 'symbol' := 'none'}
nativeArgUnit : BYTE;
{attribute 'symbol' := 'none'}
nativeArgFunction : BYTE;
{attribute 'symbol' := 'none'}
nativeArgAddress : UINT;
{attribute 'symbol' := 'none'}
nativeArgQuantity : UINT;
{attribute 'symbol' := 'none'}
nativeArgTimeout : UDINT := 1000;
{attribute 'symbol' := 'none'}
nativeSent : UDINT;
{attribute 'symbol' := 'none'}
nativeReplied : UDINT;
{attribute 'symbol' := 'none'}
nativeWindowCount : UDINT;
{attribute 'symbol' := 'none'}
nativeWindowTime : UDINT;
END_VAR
VAR_OUTPUT
out_connected : BOOL;
out_connectionState : UDINT;
out_lastNativeError : UDINT;
out_socketError : UDINT;
out_reconnectCount : UDINT;
out_registrationFull : BOOL;
END_VAR
(* ModbusMaster/atMbClient -- Implementation *)
IF nativeMode THEN
nativeCycle();
RETURN;
END_IF;
//Modbus connection
//Connects to a modbus slave
//2024-11-23 ATM Cleaned up code
//2024-12-23 CWD Add statistics
//2025-06-18 CWD Add Get priorities and interval working together
//2026-03-26 ATM Clean up code, more testing
//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests
// ===== Main function call =====
IF (in_resetStats) THEN
resetStats();
in_resetStats := FALSE;
END_IF
// ===== Connect and disconnect logic =====
//Timer to trigger a connection if not connected for to long
reconnectTimer(
IN := NOT client.xConnect AND NOT client.xConnected AND NOT client.xError,
PT := RECONNECT_DELAY
);
//Timer to trigger a disconnection if connect fails
disconnectTimer(
IN:=client.xConnect AND NOT client.xConnected,
PT:=DISCONNECT_DELAY
);
//All requests offline timer
allRequestsOfflineTON(
IN := allRequestsOffline AND client.xConnected,
PT := ALL_OFFLINE_DELAY
);
//Destroy Client delay timer
recreateClientDly(
IN:=destroyClient,
PT:=RECREATE_CLIENT_DELAY
);
IF disconnectTimer.Q OR allRequestsOfflineTON.Q OR client.xError OR recreateTest THEN
//All requests are offline
//Connection attempt failed so disconnect
//If Client has error then disconnect
connect := FALSE;
recreateTest := FALSE;
destroyClient := TRUE;
setAllRequestsOffline();
ELSIF reconnectTimer.Q AND NOT destroyClient THEN
//If not connected then attempt connect
connect := TRUE;
connectCnt := connectCnt + 1; //for debugging
END_IF;
IF recreateClientDly.Q THEN
destroyClient := FALSE;
recreateClient();
END_IF;
// If some reason gets stuck at busy, (This shouldnt happen, but force a reconnect).
forceConnect(
in := connect <> client.xConnect,
pt := FORCE_RECONNECT_DELAY
);
// Only flip status of xConnect if no current request is active.
IF (connect <> client.xConnect) AND_THEN (allRequestsComplete OR forceConnect.Q) THEN
client.xConnect := connect;
END_IF
// Moved this lower down, so xConnect happens one scan sooner.
client();
// If there are no requests, dont neeed to even do anything.
IF requestCnt = 0 THEN
numMsgSent := 0;
numMsgReply := 0;
allRequestsOnline := 0;
allRequestsOffline := 0;
RETURN;
END_IF
requestNotExecutingTON(IN := NOT requestExecuting, PT := NEXT_REQUEST_DELAY); //Only delaying 1 ms. This may not do much, was put in during testing, but now it almost seems unnecesary
// ===== Execute next request =====
IF connect AND client.xConnect AND client.xConnected AND (requestNotExecutingTON.Q OR NEXT_REQUEST_DELAY = T#0MS) THEN
//Find the lowest priority of overdue requests
nextIdx := -1;
priorityLowest := PRIORITY_NONE;
highestTimeSinceRun := 0;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF (requests[i]^.in_priorityCurrent < priorityLowest
OR (requests[i]^.in_priorityCurrent = priorityLowest AND requests[i]^.stats.sinceLastExec > highestTimeSinceRun))
AND requests[i]^.stats.sinceLastExec > TIME_TO_UDINT(requests[i]^.in_minDelay) THEN
priorityLowest := requests[i]^.in_priorityCurrent;
highestTimeSinceRun := requests[i]^.stats.sinceLastExec;
nextIdx := i;
END_IF
END_FOR
//Decriment all request priorities by one.
IF priorityLowest < PRIORITY_NONE AND priorityLowest > 0 THEN //Only if priority
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
requests[i]^.in_priorityCurrent := requests[i]^.in_priorityCurrent - 1;
IF requests[i]^.in_priorityCurrent < 0 THEN
requests[i]^.in_priorityCurrent := 0;
END_IF
END_FOR
END_IF
IF nextIdx >= 0 THEN
activeIndex := nextIdx;
END_IF
END_IF
processAllRequests();
allRequestsOnline := (activeReqCnt > 0) AND (onlineCnt = activeReqCnt);
allRequestsOffline := (activeReqCnt > 0) AND (onlineCnt = 0);
// ===== Client statistics =====
//Get a sum of done and error counts from all requests
reqErrSum := 0;
reqDoneSum := 0;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
reqDoneSum := reqDoneSum + requests[i]^.stats.doneCnt;
reqErrSum := reqErrSum + requests[i]^.stats.errorCnt;
//copy stats to array for debugging
reqStats[i] := ADR(requests[i]^.stats);
END_FOR
//Every time the tx message counter changes increment msg count and add last transaction time
IF client.udiNumMsgSent <> msgCountLast THEN
msgCountLast := client.udiNumMsgSent;
msgTimeSum := msgTimeSum + client.udiLastTransactionTime;
msgCountSum := msgCountSum + 1;
END_IF
//Every 1 second compute statistics
pulse();
IF pulse.T_Sec_OS THEN
msgLastSecondCnt := msgCountSum;
msgCountSum := 0;
msgLastSecondTime := msgTimeSum;
msgTimeSum := 0;
END_IF
numMsgSent := client.udiNumMsgSent;
numMsgReply := client.udiNumMsgReply;
(* ModbusMaster/atMbClientRTU -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK atMbClientRTU EXTENDS atMbClient
VAR_INPUT
{attribute 'symbol' := 'none'}
in_port: BYTE := 0; //0=None, 1,2,3,4
{attribute 'symbol' := 'none'}
in_baudRate: UINT := 9600; //4800, 9600, 19200, 38400, 57600, 115200
{attribute 'symbol' := 'none'}
in_parity: BYTE := 0; //0=None, 1=Odd, 2=Even
{attribute 'symbol' := 'none'}
in_dataBits: BYTE := 8; //8=8
{attribute 'symbol' := 'none'}
in_stopBits: BYTE := 1; //1=OneStopBit, 2=One5StopBits, 3=TwoStopBits
END_VAR
VAR
{attribute 'symbol' := 'none'}
_client: POINTER TO ModbusFB.ClientSerial;
END_VAR
(* ModbusMaster/atMbClientRTU -- Implementation *)
//Modbus connection
//Connects to a modbus slave
//2024-06-27 CWD Initial create
//2024-11-13 CWD Fix problem with not connecting properly on power up
//2024-11-15 ATM Move from structure to defaults.
//2024-11-23 ATM Cleaned up code
//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests
IF NOT setupClient() THEN
RETURN;
END_IF
//Set dataRate
//This is based on an 8 byte request and a 255 byte reply
dataRate := in_baudRate;
// ===== Initialization =====
// if the param changes, force a reinit
IF _client^.iPort <> in_port
OR _client^.dwBaudrate <> in_baudRate
OR _client^.byDataBits <> in_dataBits
OR _client^.eParity <> in_parity
OR _client^.eStopBits <> in_stopBits THEN
initDone := FALSE;
END_IF;
IF NOT initDone THEN
initDone := TRUE;
//Other configuration parameters
_client^.iPort := in_port;
_client^.dwBaudrate := in_baudRate;
_client^.byDataBits := in_dataBits;
_client^.eParity := in_parity;
_client^.eStopBits := in_stopBits;
_client^.eDTRcontrol := 0;
_client^.eRTScontrol := 0;
_client^.eRTUASCII := 0;
_client^.udiLogOptions := ModbusFB.LoggingOptions.All;
_client^.xConnect:=FALSE;
END_IF
SUPER^();
(* ModbusMaster/atMbClientTCP -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK atMbClientTCP EXTENDS atMbClient
VAR_INPUT
{attribute 'symbol' := 'none'}
in_ipAddress: ARRAY[0..3] OF BYTE; // TCP IP Address
{attribute 'symbol' := 'none'}
in_port: UINT := 502; // TCP port
{attribute 'symbol' := 'none'}
in_baudrate: UINT := 0; // Set this if using RS485 to TCP Converter, used to calculate timeout.
END_VAR
VAR
{attribute 'symbol' := 'none'}
_client: POINTER TO ModbusFB.ClientTCP;
END_VAR
VAR_INPUT
in_enable : BOOL := TRUE;
in_connectTimeout : TIME := T#2S;
in_reconnectDelay : TIME := T#1S;
in_holdUncertainWrites : BOOL := FALSE;
END_VAR
VAR
nativeEndpointIP : DWORD;
nativeEndpointPort : UINT;
nativeConfigConnect : UDINT;
nativeConfigRetry : UDINT;
END_VAR
(* ModbusMaster/atMbClientTCP -- Implementation *)
nativeMode := TRUE;
nativeIP := SHL(BYTE_TO_DWORD(in_ipAddress[0]),24) OR
SHL(BYTE_TO_DWORD(in_ipAddress[1]),16) OR
SHL(BYTE_TO_DWORD(in_ipAddress[2]),8) OR BYTE_TO_DWORD(in_ipAddress[3]);
nativePort := in_port;
nativeConnectTimeout := MAX(UDINT#1,TIME_TO_UDINT(in_connectTimeout));
nativeRetryDelay := MAX(UDINT#1,TIME_TO_UDINT(in_reconnectDelay));
nativeHoldUncertainWrites := in_holdUncertainWrites;
IF nativeHandle <> 0 AND ((nativeEndpointIP <> nativeIP) OR
(nativeEndpointPort <> nativePort) OR (nativeConfigConnect <> nativeConnectTimeout) OR
(nativeConfigRetry <> nativeRetryDelay) OR NOT in_enable) THEN
nativeRelease();
END_IF;
nativeEndpointIP := nativeIP; nativeEndpointPort := nativePort;
nativeConfigConnect := nativeConnectTimeout; nativeConfigRetry := nativeRetryDelay;
nativeEnable := in_enable;
IF in_baudrate <> 0 THEN dataRate := in_baudrate; ELSE dataRate := 105200; END_IF;
SUPER^();
(* ModbusMaster/atMbReqBase -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK ABSTRACT atMbReqBase
VAR_INPUT
{attribute 'symbol' := 'none'}
client: REFERENCE TO atMbClient; // Instance of atMBClient
{attribute 'symbol' := 'none'}
in_startAddr: UINT;
{attribute 'symbol' := 'none'}
in_quantity: UINT;
{attribute 'symbol' := 'none'}
in_dataOffset: UINT; // Offset position for data into array.
{attribute 'symbol' := 'none'}
in_unitId: BYTE := 0;
{attribute 'symbol' := 'none'}
in_timeout: SYSTIME := 0; // timeout in ms, if not set, will be 1ms less than interval.
{attribute 'symbol' := 'none'}
in_minDelay: TIME := T#10MS; // This is the minimum delay between queries, only relevent if high priority.
{attribute 'symbol' := 'none'}
in_prioritySetpoint: INT := 50; // this requests priority compared to other requests. Lower value is higher priority.
{attribute 'symbol' := 'none'}
in_priorityCurrent: INT; //Current priority. This is decrimented and the request is executed when at zero.
{attribute 'symbol' := 'none'}
stats: atMbRequestStats; //Request stats
END_VAR
VAR_OUTPUT
//{attribute 'symbol' := 'none'}
//out_fault: BOOL;
END_VAR
VAR
{attribute 'symbol' := 'none'}
dataSize: UINT;
//{attribute 'symbol' := 'none'}
//quantUsed: UINT;
//{attribute 'symbol' := 'none'}
//doneTrig: R_TRIG; //Succesful request trigger
//{attribute 'symbol' := 'none'}
//errTrig: R_TRIG; //Error request result trigger
{attribute 'symbol' := 'none'}
request: REFERENCE TO ModbusFB.ClientRequest;;
END_VAR
VAR
nativeFunction : BYTE;
nativeCount : UINT;
nativeOffset : UINT;
nativeInFlight : BOOL;
nativeReadComplete : BOOL;
nativeCopyAllowed : BOOL;
nativeWriteHold : BOOL;
nativeStartMs : ULINT;
nativeLoop : DINT;
nativeRC : UDINT;
END_VAR
VAR_OUTPUT
out_nativeError : UDINT;
out_socketError : UDINT;
out_writeUncertain : BOOL;
out_validData : BOOL;
out_dataAgeMs : ULINT;
out_stale : BOOL := TRUE;
out_parameterError : BOOL;
END_VAR
VAR_INPUT
in_staleAfter : TIME := T#2S;
END_VAR
(* ModbusMaster/atMbReqBase -- Implementation *)
//Modbus request
//Makes a request using the start and quantity, puts the result into data
//2024-06-27 CWD Initial create
//2024-11-15 ATM Moved from Work to main for easier debugging
//2024-11-23 ATM Cleaned up code, added start array position
//2025-10-28 CWD Updated to reduce symbol usage
IF NOT __ISVALIDREF(client) THEN
RETURN;
END_IF
//Add this request to the clients request array
client.addRequest(THIS);
//fault logic
//Fault if error
//errTrig(CLK := stats.reqError);
//IF errTrig.Q THEN
// out_fault := TRUE;
//END_IF
//not faulted if a succesfull request
//doneTrig(CLK := stats.reqDone);
// also clear fault if not active
//IF doneTrig.Q OR NOT stats.active THEN
// out_fault := FALSE;
//END_IF
IF in_quantity <= dataSize THEN
stats.reqLength := in_quantity;
ELSE
stats.reqLength := dataSize;
END_IF;
(* ModbusMaster/atMbReqReadCoil -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK atMbReqReadCoil EXTENDS atMbReqBase
VAR_IN_OUT
// ARRAY[] OF BOOL to hold received data
{attribute 'symbol' := 'none'}
data: ARRAY[*] OF BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'symbol' := 'none'}
readCoilReq: POINTER TO ModbusFB.ClientRequestReadCoils;
END_VAR
(* ModbusMaster/atMbReqReadCoil -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
out_parameterError := (UINT_TO_DINT(in_dataOffset) < LOWER_BOUND(data,1)) OR
(UINT_TO_DINT(in_dataOffset) > UPPER_BOUND(data,1));
IF out_parameterError THEN dataSize := 0;
ELSE dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - UINT_TO_DINT(in_dataOffset) + 1);
END_IF;
SUPER^();
RETURN;
END_IF;
//Modbus request input registers
//2024-11-16 CWD Added read coil
//2024-11-23 ATM Cleaned up code, added start array position
//2025-10-28 CWD Updated to reduce symbol usage
//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests
//Verify data size is large enough, if not then only get as many registers that will fit.
dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1);
SUPER^();
(* ModbusMaster/atMbReqReadDiscrete -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK atMbReqReadDiscrete EXTENDS atMbReqBase
VAR_IN_OUT
// ARRAY[] OF BOOL to hold received data
{attribute 'symbol' := 'none'}
data: ARRAY[*] OF BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'symbol' := 'none'}
readDiscreteReq: POINTER TO ModbusFB.ClientRequestReadDiscreteInputs;
END_VAR
(* ModbusMaster/atMbReqReadDiscrete -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
out_parameterError := (UINT_TO_DINT(in_dataOffset) < LOWER_BOUND(data,1)) OR
(UINT_TO_DINT(in_dataOffset) > UPPER_BOUND(data,1));
IF out_parameterError THEN dataSize := 0;
ELSE dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - UINT_TO_DINT(in_dataOffset) + 1);
END_IF;
SUPER^();
RETURN;
END_IF;
//Modbus request input registers
//2024-06-27 CWD Initial Create
//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave
//2024-11-15 ATM Moved from Work to main for easier debugging
//2024-11-23 ATM Cleaned up code, added start array position
//2025-10-28 CWD Updated to reduce symbol usage
//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests
//Verify data size is large enough, if not then only get as many registers that will fit.
dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1);
SUPER^();
(* ModbusMaster/atMbReqReadHolding -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK atMbReqReadHolding EXTENDS atMbReqBase
VAR_IN_OUT
// ARRAY[] OF UINT to hold received data
{attribute 'symbol' := 'none'}
data: ARRAY[*] OF UINT;
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'symbol' := 'none'}
readHoldReq: POINTER TO ModbusFB.ClientRequestReadHoldingRegisters;
END_VAR
(* ModbusMaster/atMbReqReadHolding -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
out_parameterError := (UINT_TO_DINT(in_dataOffset) < LOWER_BOUND(data,1)) OR
(UINT_TO_DINT(in_dataOffset) > UPPER_BOUND(data,1));
IF out_parameterError THEN dataSize := 0;
ELSE dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - UINT_TO_DINT(in_dataOffset) + 1);
END_IF;
SUPER^();
RETURN;
END_IF;
//Modbus request input registers
//2024-06-27 CWD Initial Create
//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave
//2024-11-15 ATM Moved from Work to main for easier debugging
//2024-11-23 ATM Cleaned up code, added start array position
//2025-10-28 CWD Updated to reduce symbol usage
//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests
//Verify data size is large enough, if not then only get as many registers that will fit.
dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1);
SUPER^();
(* ModbusMaster/atMbReqReadInput -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK atMbReqReadInput EXTENDS atMbReqBase
VAR_IN_OUT
// ARRAY[] OF UINT to hold received data
{attribute 'symbol' := 'none'}
data: ARRAY[*] OF UINT;
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'symbol' := 'none'}
readInputReq: POINTER TO ModbusFB.ClientRequestReadInputRegisters;
END_VAR
(* ModbusMaster/atMbReqReadInput -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
out_parameterError := (UINT_TO_DINT(in_dataOffset) < LOWER_BOUND(data,1)) OR
(UINT_TO_DINT(in_dataOffset) > UPPER_BOUND(data,1));
IF out_parameterError THEN dataSize := 0;
ELSE dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - UINT_TO_DINT(in_dataOffset) + 1);
END_IF;
SUPER^();
RETURN;
END_IF;
//Modbus request input registers
//2024-06-27 CWD Initial Create
//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave
//2024-11-15 ATM Moved from Work to main for easier debugging
//2024-11-23 ATM Cleaned up code, added start array position
//2025-10-28 CWD Updated to reduce symbol usage
//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests
//Verify data size is large enough, if not then only get as many registers that will fit.
dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1);
SUPER^();
(* ModbusMaster/atMbRequestStats -- Interface *)
{attribute 'monitoring_display' := 'toString'}
{attribute 'no_assign'}
FUNCTION_BLOCK atMbRequestStats
VAR_INPUT
lastErrorId: modbusFB.Error; //Last error id
lastExceptionCode: atMBExceptionCodes; //Last modbus exception code. These are the standard modbus exceptions.
lastValidDuration: UDINT; // How long last transaction took (ms)
longestValidDuration: UDINT; //Longest valid duration (ms)
interval: UDINT; // Start-to-start interval (ms)
offlineCnt: UINT; //Count of how many times this request has gone offline
doneCnt: UDINT; // Count of successful request
errorCnt: UDINT; // Count of failed requests
doneCntRst: UDINT; // Count of succesful requests, resets on error result
errorCntRst: UDINT; // Count of failed requests, resets on succesful request
sinceLastExec: UDINT; // Time since the last start (ms)
timeoutCnt: UDINT;
reqLength: UINT; //Number of registers requested in request, used to calc active.
END_VAR
VAR_OUTPUT
executeLight: BOOL; //Execute that stays on momentarily
reqDoneLight: BOOL; //Request done that stays on momentarily
reqErrorLight: BOOL; //Request error that stays on momentarily
reqBusyLight: BOOL; //Request busy that stays on momentarily
online: BOOL; //Online means the requests are being replied to normally or with modbus exceptions. Communications are ok.
offline: BOOL; //Opposite of online, but starts out as false on program start.
active: BOOL; // This is true if its actually being used, and not having a count of 0 on the request.
executeMbFb: BOOL;
END_VAR
VAR
{attribute 'symbol' := 'none'}
inDoneDeb: atDebounce;
{attribute 'symbol' := 'none'}
inErrDeb: atDebounce;
{attribute 'symbol' := 'none'}
inBusyDeb: atDebounce;
{attribute 'symbol' := 'none'}
executeDeb: atDebounce;
toString: STRING(20);
{attribute 'symbol' := 'none'}
executeTrig: R_TRIG; //Execute trigger
{attribute 'symbol' := 'none'}
offlineTrig: R_TRIG; //Offline trigger
{attribute 'symbol' := 'none'}
lastReqStart: SYSTIME; //time of start of last request
{attribute 'symbol' := 'none'}
TSSus: SYSTIME;
_execute: BOOL; //Execute trigger and status. Turn on to trigger request. Will turn off when request is complete
_reqDone: BOOL; //Request done
_reqBusy: BOOL; //Request busy
_reqError: BOOL; //Request error
END_VAR
VAR CONSTANT
{attribute 'symbol' := 'none'}
lightOn: TIME:= T#100MS; //Light on time for debug indicators
END_VAR
VAR_INPUT
nativeResponseSeen : BOOL;
nativeLastSuccessMs : ULINT;
END_VAR
(* ModbusMaster/atMbRequestStats -- Implementation *)
SysTimeGetUs(TSSus);
IF (reqLength = 0) THEN
active := FALSE;
doneCnt := 0;
errorCnt := 0;
lastErrorId := modbusFB.Error.OK;
lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS;
execute := FALSE;
online := FALSE;
offline := FALSE;
reqDone := FALSE;
reqBusy := FALSE;
reqError := FALSE;
sinceLastExec := 0;
RETURN;
END_IF
active := TRUE;
inDoneDeb(in_raw := reqDone, in_delOn := T#0S, in_DelOff := lightOn);
reqDoneLight := inDoneDeb.out_val;
inBusyDeb(in_raw := reqBusy, in_delOn := T#0S, in_DelOff := lightOn);
reqBusyLight := inBusyDeb.out_val;
inErrDeb(in_raw := reqError, in_delOn := T#0S, in_DelOff := lightOn);
reqErrorLight := inErrDeb.out_val;
//Debounced execute indicator for debbugging
executeDeb(in_raw := _execute, in_delOn := T#0S, in_DelOff := lightOn);
executeLight := executeDeb.out_val;
//Set online status
IF nativeResponseSeen OR doneCntRst >=3 THEN
online := TRUE;
offline := FALSE;
END_IF
//Set online status
IF NOT nativeResponseSeen AND errorCntRst >= 3 THEN
online := FALSE;
offline := TRUE;
END_IF
//Record start time in ms on execute rising edge
executeTrig(CLK := execute);
IF executeTrig.Q THEN
interval := ULINT_TO_UDINT((TSSus - lastReqStart)/1000);
lastReqStart := TSSus;
END_IF
//Offline count
offlineTrig(CLK := NOT online);
IF offlineTrig.Q THEN
offlineCnt := offlineCnt + 1;
END_IF
//Calculate ms since last execute
//If the last request start is more then 1 minute ago then set it for 1 minute
IF TSSus >= lastReqStart THEN
IF TSSus - lastReqStart > 60000000 THEN lastReqStart := TSSus - 60000000; END_IF;
ELSE lastReqStart := TSSus;
END_IF;
sinceLastExec := ULINT_TO_UDINT((TSSus - lastReqStart)/1000);
(* ModbusMaster/atMbReqWriteCoil -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK atMbReqWriteCoil EXTENDS atMbReqBase
VAR_IN_OUT
// ARRAY[] OF BOOL to hold received data
{attribute 'symbol' := 'none'}
data: ARRAY[*] OF BOOL;
END_VAR
VAR
{attribute 'symbol' := 'none'}
writeCoilsReq: POINTER TO ModbusFB.ClientRequestWriteMultipleCoils;
END_VAR
(* ModbusMaster/atMbReqWriteCoil -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
out_parameterError := (UINT_TO_DINT(in_dataOffset) < LOWER_BOUND(data,1)) OR
(UINT_TO_DINT(in_dataOffset) > UPPER_BOUND(data,1));
IF out_parameterError THEN dataSize := 0;
ELSE dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - UINT_TO_DINT(in_dataOffset) + 1);
END_IF;
SUPER^();
RETURN;
END_IF;
//Modbus request input registers
//2024-06-27 CWD Initial Create
//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave
//2024-11-15 ATM Moved from Work to main for easier debugging
//2024-11-23 ATM Cleaned up code, added start array position
//2025-10-28 CWD Updated to reduce symbol usage
//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests
//Verify data size is large enough, if not then only get as many registers that will fit.
dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1);
SUPER^();
(* ModbusMaster/atMbReqWriteHolding -- Interface *)
{attribute 'no_assign'}
FUNCTION_BLOCK atMbReqWriteHolding EXTENDS atMbReqBase
VAR_IN_OUT
// ARRAY[] OF UINT to hold received data
{attribute 'symbol' := 'none'}
data: ARRAY[*] OF UINT;
END_VAR
VAR_OUTPUT
END_VAR
VAR
{attribute 'symbol' := 'none'}
writeHoldReq: POINTER TO ModbusFB.ClientRequestWriteMultipleRegisters;
END_VAR
(* ModbusMaster/atMbReqWriteHolding -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
out_parameterError := (UINT_TO_DINT(in_dataOffset) < LOWER_BOUND(data,1)) OR
(UINT_TO_DINT(in_dataOffset) > UPPER_BOUND(data,1));
IF out_parameterError THEN dataSize := 0;
ELSE dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - UINT_TO_DINT(in_dataOffset) + 1);
END_IF;
SUPER^();
RETURN;
END_IF;
//Modbus request input registers
//2024-06-27 CWD Initial Create
//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave
//2024-11-15 ATM Moved from Work to main for easier debugging
//2024-11-23 ATM Cleaned up code, added start array position
//2025-10-28 CWD Updated to reduce symbol usage
//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests
//Verify data size is large enough, if not then only get as many registers that will fit.
dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1);
SUPER^();
(* ModbusMaster/To_IPAddress -- Interface *)
FUNCTION To_IPAddress : ARRAY[0..3] OF BYTE
VAR_INPUT
byte1: BYTE;
byte2: BYTE;
byte3: BYTE;
byte4: BYTE;
END_VAR
(* ModbusMaster/To_IPAddress -- Implementation *)
//Convert parameters to array
To_IPAddress[0] := byte1;
To_IPAddress[1] := byte2;
To_IPAddress[2] := byte3;
To_IPAddress[3] := byte4;
(* atMbClient/addRequest -- Interface *)
//Adds the specified request to the global list
METHOD PUBLIC addRequest
VAR_INPUT
request: POINTER TO atMbReqBase;
END_VAR
VAR
i: UINT := 0;
END_VAR
(* atMbClient/addRequest -- Implementation *)
IF request = 0 THEN RETURN; END_IF;
IF requestCnt > 0 THEN
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF request = requests[i] THEN RETURN; END_IF;
END_FOR;
END_IF;
IF requestCnt >= 100 THEN
out_registrationFull := TRUE;
RETURN;
END_IF;
requests[requestCnt] := request;
requestCnt := requestCnt + 1;
(* atMbClient/processAllRequests -- Interface *)
METHOD processAllRequests
VAR_INPUT
END_VAR
VAR
i: DINT; // Loop bstisnlr
END_VAR
(* atMbClient/processAllRequests -- Implementation *)
IF requestCnt = 0 OR requestCnt > 100 THEN RETURN; END_IF;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[i] <> 0 THEN
requests[i]^.processRequest();
END_IF;
END_FOR;
(* atMbClient/recreateAllRequests -- Interface *)
METHOD PROTECTED recreateAllRequests
VAR_INPUT
END_VAR
VAR
i : DINT;
END_VAR
(* atMbClient/recreateAllRequests -- Implementation *)
IF requestCnt = 0 OR requestCnt > 100 THEN RETURN; END_IF;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[i] <> 0 THEN
requests[i]^.recreateRequest();
END_IF;
END_FOR;
(* atMbClient/recreateClient -- Interface *)
METHOD ABSTRACT recreateClient
VAR_INPUT
END_VAR
(* atMbClient/recreateClient -- Implementation *)
(* atMbClient/resetStats -- Interface *)
METHOD resetStats
VAR_INPUT
END_VAR
VAR
i: DINT;
val: POINTER TO UDINT;
END_VAR
(* atMbClient/resetStats -- Implementation *)
IF nativeMode THEN
nativeSent := 0; nativeReplied := 0;
nativeWindowCount := 0; nativeWindowTime := 0;
msgLastSecondCnt := 0; msgLastSecondTime := 0;
numMsgSent := 0; numMsgReply := 0;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
requests[i]^.stats.resetStats();
END_FOR;
RETURN;
END_IF;
connectCnt := 0;
val := ADR(client.udiNumMsgSent);
val^ := 0;
val := ADR(client.udiNumMsgReply);
val^ := 0;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
requests[i]^.stats.resetStats();
END_FOR;
(* atMbClient/setAllRequestsOffline -- Interface *)
METHOD PROTECTED setAllRequestsOffline : BOOL
VAR_INPUT
END_VAR
VAR
i: DINT;
END_VAR
(* atMbClient/setAllRequestsOffline -- Implementation *)
IF requestCnt = 0 OR requestCnt > 100 THEN RETURN; END_IF;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[i] <> 0 THEN
requests[i]^.stats.setClientOffline();
END_IF;
END_FOR;
(* atMbClient/setupClient -- Interface *)
METHOD setupClient : BOOL
VAR_INPUT
END_VAR
(* atMbClient/setupClient -- Implementation *)
IF nativeMode THEN
setupClient := nativeHandle <> 0;
RETURN;
END_IF;
// Only create request if doesnt exist
IF NOT __ISVALIDREF(client) THEN
recreateClient();
IF NOT __ISVALIDREF(client) THEN
atGVL.dynamicAllocationFail.in_raw := TRUE;
setupClient := FALSE;
RETURN;
END_IF
END_IF
setupClient := TRUE;
(* atMbClient/activeIndex -- Interface *)
PROPERTY activeIndex : DINT
(* atMbClient/activeReqCnt -- Interface *)
PROPERTY PRIVATE activeReqCnt : UINT
(* atMbClient/allRequestsComplete -- Interface *)
{attribute 'symbol' := 'none'}
PROPERTY PRIVATE allRequestsComplete : BOOL
(* atMbClient/onlineCnt -- Interface *)
{attribute 'symbol' := 'none'}
PROPERTY onlineCnt : DINT//Count of requests that are online
(* atMbClient/requestExecuting -- Interface *)
{attribute 'symbol' := 'none'}
PROPERTY PRIVATE requestExecuting : BOOL //There is at least one request executing
(* atMbClientRTU/recreateClient -- Interface *)
METHOD recreateClient
VAR_INPUT
old_client_id: POINTER TO ModbusFB.ClientSerial;
END_VAR
(* atMbClientRTU/recreateClient -- Implementation *)
old_client_id := _client;
_client := __NEW(ModbusFB.ClientSerial);
IF _client = 0 THEN
atGVL.dynamicAllocationFail.in_raw := TRUE;
END_IF;
client REF= _client^;
connect := FALSE;
IF old_client_id <> 0 THEN
__DELETE(old_client_id);
old_client_id := 0;
END_IF
recreateAllRequests();
(* atMbClientRTU/toString -- Interface *)
{attribute 'symbol' := 'none'}
PROPERTY toString : atMbClientToString //Client request status noneOk = no requests working, someOk = some requests are working some are failing, allOk = all requests are working
(* atMbClientTCP/recreateClient -- Interface *)
METHOD recreateClient
VAR
END_VAR
(* atMbClientTCP/recreateClient -- Implementation *)
nativeRelease();
// Next scan creates a fresh native context with current endpoint settings.
(* atMbReqBase/busy -- Interface *)
METHOD busy : BOOL
VAR_INPUT
in: BOOL;
END_VAR
VAR
executeTime: ULINT;
replyTimeoutTime: ULINT;
END_VAR
(* atMbReqBase/busy -- Implementation *)
stats.reqBusy := in;
IF NOT in THEN
RETURN;
END_IF;
(* atMbReqBase/completeRequest -- Interface *)
METHOD completeRequest
VAR_INPUT
END_VAR
(* atMbReqBase/completeRequest -- Implementation *)
done(request.xDone);
err(in := request.xError, errorId := request.eErrorId, exceptionCode := request.eException);
busy(in := request.xBusy);
stats();
IF (atGVL.reset) THEN
// request.AbortModel();
request.ResetModel(TRUE);
END_IF
(* atMbReqBase/done -- Interface *)
METHOD done
VAR_INPUT
// Done state
in: BOOL;
END_VAR
(* atMbReqBase/done -- Implementation *)
stats.reqDone := in;
IF NOT in THEN
RETURN;
END_IF
//Set priority for next request
in_priorityCurrent := in_prioritySetpoint;
(* atMbReqBase/err -- Interface *)
METHOD err
VAR_INPUT
// Error state
in: BOOL;
errorId: modbusFB.Error; //modbusFB error ID
exceptionCode: modbusFB.ExceptionCodes; //ModbusFB exception code
END_VAR
VAR
priorityMultiplier: INT; //Raise priority when not response
END_VAR
(* atMbReqBase/err -- Implementation *)
stats.reqError := in;
IF NOT in THEN
RETURN;
END_IF
//Update error info
stats.lastErrorId := errorId;
stats.lastExceptionCode := ANY_TO_BYTE(exceptionCode);
//Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error.
//IF stats.lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN
//Since there was no response from the device set the priority to higher number priority
// in_priorityCurrent := in_prioritySetPoint * 5;
//END_IF
//Set priority for next request, priority gets higher after each error result
priorityMultiplier := UDINT_TO_INT(stats.errorCntRst);
IF priorityMultiplier > 3 THEN
priorityMultiplier := 3;
END_IF
// set multiplier to exponential retry.
in_priorityCurrent := 10 * priorityMultiplier * priorityMultiplier;
//in_priorityCurrent := in_prioritySetpoint * priorityMultiplier;
// NO idea what Reset Model does, hoping it fixes comms issues
// request.ResetModel(TRUE); // I dont think this helped
(* atMbReqBase/processRequest -- Interface *)
METHOD ABSTRACT processRequest
VAR_INPUT
END_VAR
(* atMbReqBase/processRequest -- Implementation *)
(* atMbReqBase/recreateRequest -- Interface *)
METHOD ABSTRACT recreateRequest : POINTER TO ModbusFB.ClientRequest
VAR_INPUT
END_VAR
(* atMbReqBase/recreateRequest -- Implementation *)
(* atMbReqBase/setupClient -- Interface *)
METHOD setupClient : BOOL
VAR_INPUT
in_request: POINTER TO ModbusFB.ClientRequest;
END_VAR
(* atMbReqBase/setupClient -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
setupClient := FALSE; // Native path has no ModbusFB request object.
RETURN;
END_IF;
// Only create request if doesnt exist
IF in_request = 0 THEN
in_request := recreateRequest();
IF in_request = 0 THEN
atGVL.dynamicAllocationFail.in_raw := TRUE;
setupClient := FALSE;
RETURN;
END_IF
END_IF
IF NOT __ISVALIDREF(mbsClient) THEN
setupClient := FALSE;
RETURN;
END_IF
request REF= in_request^;
setupClient := TRUE;
RETURN;
(* atMbReqBase/mbsClient -- Interface *)
PROPERTY PROTECTED mbsClient : REFERENCE TO ModbusFB.Client
(* atMbReqBase/replyTimeout -- Interface *)
//Calculates the reply timout setting
PROPERTY PROTECTED replyTimeout : UDINT
(* atMbReqReadCoil/processRequest -- Interface *)
METHOD processRequest
(* atMbReqReadCoil/processRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
nativeCopyAllowed := (UINT_TO_DINT(nativeOffset) >= LOWER_BOUND(data,1)) AND
(UINT_TO_DINT(nativeOffset) + UINT_TO_DINT(nativeCount) - 1 <= UPPER_BOUND(data,1));
nativeProcess(1);
IF nativeReadComplete AND nativeCopyAllowed THEN
FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO
data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0;
END_FOR;
END_IF;
RETURN;
END_IF;
IF NOT setupClient(readCoilReq) THEN
RETURN;
END_IF
//Do request processing
readCoilReq^(
rClient := mbsClient,
xExecute := stats.execute,
udiTimeOut := 0,
uiUnitId := in_unitId,
udiReplyTimeout := replyTimeout,
uiStartItem := in_startAddr,
uiQuantity := stats.reqLength,
pData := ADR(data[in_dataOffset])
);
completeRequest();
(* atMbReqReadCoil/recreateRequest -- Interface *)
METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest
VAR_INPUT
END_VAR
(* atMbReqReadCoil/recreateRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF;
nativeInFlight := FALSE;
recreateRequest := 0;
RETURN;
END_IF;
IF readCoilReq <> 0 THEN
__DELETE(readCoilReq);
readCoilReq := 0;
END_IF
readCoilReq := __NEW(ModbusFB.ClientRequestReadCoils);
recreateRequest := readCoilReq;
(* atMbReqReadDiscrete/processRequest -- Interface *)
METHOD processRequest
(* atMbReqReadDiscrete/processRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
nativeCopyAllowed := (UINT_TO_DINT(nativeOffset) >= LOWER_BOUND(data,1)) AND
(UINT_TO_DINT(nativeOffset) + UINT_TO_DINT(nativeCount) - 1 <= UPPER_BOUND(data,1));
nativeProcess(2);
IF nativeReadComplete AND nativeCopyAllowed THEN
FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO
data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0;
END_FOR;
END_IF;
RETURN;
END_IF;
IF NOT setupClient(readDiscreteReq) THEN
RETURN;
END_IF
//Do request processing
readDiscreteReq^(
rClient := mbsClient,
xExecute := stats.execute,
udiTimeOut := 0,
uiUnitId := in_unitId,
udiReplyTimeout := replyTimeout,
uiStartItem := in_startAddr,
uiQuantity := stats.reqLength,
pData := ADR(data[in_dataOffset])
);
completeRequest();
(* atMbReqReadDiscrete/recreateRequest -- Interface *)
METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest
VAR_INPUT
END_VAR
(* atMbReqReadDiscrete/recreateRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF;
nativeInFlight := FALSE;
recreateRequest := 0;
RETURN;
END_IF;
IF readDiscreteReq <> 0 THEN
__DELETE(readDiscreteReq);
readDiscreteReq := 0;
END_IF
readDiscreteReq := __NEW(ModbusFB.ClientRequestReadDiscreteInputs);
recreateRequest := readDiscreteReq;
(* atMbReqReadHolding/processRequest -- Interface *)
METHOD processRequest
(* atMbReqReadHolding/processRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
nativeCopyAllowed := (UINT_TO_DINT(nativeOffset) >= LOWER_BOUND(data,1)) AND
(UINT_TO_DINT(nativeOffset) + UINT_TO_DINT(nativeCount) - 1 <= UPPER_BOUND(data,1));
nativeProcess(3);
IF nativeReadComplete AND nativeCopyAllowed THEN
FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO
data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]);
END_FOR;
END_IF;
RETURN;
END_IF;
IF NOT setupClient(readHoldReq) THEN
RETURN;
END_IF
//Do request processing
readHoldReq^(
rClient := mbsClient,
xExecute := stats.execute,
udiTimeOut := 0,
uiUnitId := in_unitId,
// udiReplyTimeout := 1 * 1000 * 1000,
udiReplyTimeout := replyTimeout,
uiStartItem := in_startAddr,
uiQuantity := stats.reqLength,
pData := ADR(data[in_dataOffset])
);
completeRequest();
(* atMbReqReadHolding/recreateRequest -- Interface *)
METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest
VAR_INPUT
END_VAR
(* atMbReqReadHolding/recreateRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF;
nativeInFlight := FALSE;
recreateRequest := 0;
RETURN;
END_IF;
IF readHoldReq <> 0 THEN
__DELETE(readHoldReq);
readHoldReq := 0;
END_IF
readHoldReq := __NEW(ModbusFB.ClientRequestReadHoldingRegisters);
recreateRequest := readHoldReq;
(* atMbReqReadInput/processRequest -- Interface *)
METHOD processRequest
(* atMbReqReadInput/processRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
nativeCopyAllowed := (UINT_TO_DINT(nativeOffset) >= LOWER_BOUND(data,1)) AND
(UINT_TO_DINT(nativeOffset) + UINT_TO_DINT(nativeCount) - 1 <= UPPER_BOUND(data,1));
nativeProcess(4);
IF nativeReadComplete AND nativeCopyAllowed THEN
FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO
data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]);
END_FOR;
END_IF;
RETURN;
END_IF;
IF NOT setupClient(readInputReq) THEN
RETURN;
END_IF
//Do request processing
readInputReq^(
rClient := mbsClient,
xExecute := stats.execute,
udiTimeOut := 0,
uiUnitId := in_unitId,
udiReplyTimeout := replyTimeout,
uiStartItem := in_startAddr,
uiQuantity := stats.reqLength,
pData := ADR(data[in_dataOffset])
);
completeRequest();
(* atMbReqReadInput/recreateRequest -- Interface *)
METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest
VAR_INPUT
END_VAR
(* atMbReqReadInput/recreateRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF;
nativeInFlight := FALSE;
recreateRequest := 0;
RETURN;
END_IF;
IF readInputReq <> 0 THEN
__DELETE(readInputReq);
readInputReq := 0;
END_IF
readInputReq := __NEW(ModbusFB.ClientRequestReadInputRegisters);
recreateRequest := readInputReq;
(* atMbRequestStats/resetStats -- Interface *)
METHOD resetStats
VAR_INPUT
END_VAR
(* atMbRequestStats/resetStats -- Implementation *)
doneCnt := 0;
doneCntRst := 0;
errorCnt := 0;
errorCntRst := 0;
(* atMbRequestStats/setClientOffline -- Interface *)
METHOD setClientOffline : BOOL
VAR_INPUT
END_VAR
(* atMbRequestStats/setClientOffline -- Implementation *)
nativeResponseSeen := FALSE;
doneCntRst := 0;
online := FALSE;
offline := TRUE;
(* atMbRequestStats/setToString -- Interface *)
METHOD PRIVATE setToString : BOOL
VAR_INPUT
END_VAR
(* atMbRequestStats/setToString -- Implementation *)
toString := CONCAT('OK:', CONCAT(TO_STRING(doneCnt), CONCAT('/Er:', TO_STRING(errorCnt))));
(* atMbRequestStats/execute -- Interface *)
PROPERTY execute : BOOL
(* atMbRequestStats/reqBusy -- Interface *)
PROPERTY reqBusy : BOOL
(* atMbRequestStats/reqDone -- Interface *)
PROPERTY reqDone : BOOL
(* atMbRequestStats/reqError -- Interface *)
PROPERTY reqError : BOOL
(* atMbReqWriteCoil/processRequest -- Interface *)
METHOD processRequest
(* atMbReqWriteCoil/processRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
nativeCopyAllowed := (UINT_TO_DINT(nativeOffset) >= LOWER_BOUND(data,1)) AND
(UINT_TO_DINT(nativeOffset) + UINT_TO_DINT(nativeCount) - 1 <= UPPER_BOUND(data,1));
IF stats.execute AND NOT nativeInFlight AND client.nativeSelected = THIS AND NOT out_parameterError THEN
FOR nativeLoop := 0 TO UINT_TO_DINT(stats.reqLength)-1 DO
client.nativeIO[nativeLoop] := BOOL_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]);
END_FOR;
END_IF;
nativeProcess(15);
RETURN;
END_IF;
IF NOT setupClient(writeCoilsReq) THEN
RETURN;
END_IF
//Do request processing
writeCoilsReq^(
rClient := mbsClient,
xExecute := stats.execute,
udiTimeOut := 0,
uiUnitId := in_unitId,
udiReplyTimeout := replyTimeout,
uiStartItem := in_startAddr,
uiQuantity := stats.reqLength,
pData := ADR(data[in_dataOffset])
);
completeRequest();
(* atMbReqWriteCoil/recreateRequest -- Interface *)
METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest
VAR_INPUT
END_VAR
(* atMbReqWriteCoil/recreateRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF;
nativeInFlight := FALSE;
recreateRequest := 0;
RETURN;
END_IF;
IF writeCoilsReq <> 0 THEN
__DELETE(writeCoilsReq);
writeCoilsReq := 0;
END_IF
writeCoilsReq := __NEW(ModbusFB.ClientRequestWriteMultipleCoils);
recreateRequest := writeCoilsReq;
(* atMbReqWriteCoil/writeNow -- Interface *)
METHOD writeNow : BOOL
VAR_INPUT
END_VAR
(* atMbReqWriteCoil/writeNow -- Implementation *)
nativeWriteHold := FALSE;
out_writeUncertain := FALSE;
in_priorityCurrent := 0;
(* atMbReqWriteHolding/processRequest -- Interface *)
METHOD processRequest
(* atMbReqWriteHolding/processRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
nativeCopyAllowed := (UINT_TO_DINT(nativeOffset) >= LOWER_BOUND(data,1)) AND
(UINT_TO_DINT(nativeOffset) + UINT_TO_DINT(nativeCount) - 1 <= UPPER_BOUND(data,1));
IF stats.execute AND NOT nativeInFlight AND client.nativeSelected = THIS AND NOT out_parameterError THEN
FOR nativeLoop := 0 TO UINT_TO_DINT(stats.reqLength)-1 DO
client.nativeIO[nativeLoop] := UINT_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]);
END_FOR;
END_IF;
nativeProcess(16);
RETURN;
END_IF;
IF NOT setupClient(writeHoldReq) THEN
RETURN;
END_IF
//Do request processing
writeHoldReq^(
rClient := mbsClient,
xExecute := stats.execute,
udiTimeOut := 0,
uiUnitId := in_unitId,
udiReplyTimeout := replyTimeout,
uiStartItem := in_startAddr,
uiQuantity := stats.reqLength,
pData := ADR(data[in_dataOffset])
);
completeRequest();
(* atMbReqWriteHolding/recreateRequest -- Interface *)
METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest
VAR_INPUT
END_VAR
(* atMbReqWriteHolding/recreateRequest -- Implementation *)
IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF;
nativeInFlight := FALSE;
recreateRequest := 0;
RETURN;
END_IF;
IF writeHoldReq <> 0 THEN
__DELETE(writeHoldReq);
writeHoldReq := 0;
END_IF
writeHoldReq := __NEW(ModbusFB.ClientRequestWriteMultipleRegisters);
recreateRequest := writeHoldReq;
(* atMbReqWriteHolding/writeNow -- Interface *)
METHOD writeNow : BOOL
VAR_INPUT
END_VAR
(* atMbReqWriteHolding/writeNow -- Implementation *)
nativeWriteHold := FALSE;
out_writeUncertain := FALSE;
in_priorityCurrent := 0;
(* activeIndex/Get -- Interface *)
VAR
i: DINT; //Loop variable
END_VAR
(* activeIndex/Get -- Implementation *)
IF nativeMode THEN
activeIndex := nativeActiveIndex;
RETURN;
END_IF;
// ===== Test for any requests executing =====
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[i]^.stats.execute OR requests[i]^.stats.reqBusy THEN
activeIndex := i;
RETURN;
END_IF
END_FOR
activeIndex := -1;
(* activeIndex/Set -- Interface *)
VAR
END_VAR
(* activeIndex/Set -- Implementation *)
// set request to active, which is how the active index gets calculated.
requests[activeIndex]^.stats.execute := TRUE;
(* activeReqCnt/Get -- Interface *)
VAR
i: UINT; //Loop variable
cnt: UINT;
END_VAR
(* activeReqCnt/Get -- Implementation *)
cnt := 0;
IF requestCnt = 0 THEN
activeReqCnt := 0;
RETURN;
END_IF
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
//Test for any offline request
IF requests[i]^.stats.active THEN
cnt := cnt + 1;
END_IF
END_FOR;
activeReqCnt := cnt;
(* allRequestsComplete/Get -- Interface *)
VAR
i: UINT;
END_VAR
(* allRequestsComplete/Get -- Implementation *)
IF requestCnt = 0 THEN
allRequestsComplete := 0;
RETURN;
END_IF
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
//Test for any offline request
IF requests[i]^.stats.execute OR requests[i]^.stats.reqDone OR requests[i]^.stats.reqBusy OR requests[i]^.stats.reqError THEN
allRequestsComplete := FALSE;
RETURN;
END_IF
END_FOR;
allRequestsComplete := TRUE;
(* onlineCnt/Get -- Interface *)
VAR
i: UINT; //Loop variable
cnt: UINT;
END_VAR
(* onlineCnt/Get -- Implementation *)
cnt := 0;
IF requestCnt = 0 THEN
onlineCnt := 0;
RETURN;
END_IF
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
//Test for any offline request
IF requests[i]^.stats.online THEN
cnt := cnt + 1;
END_IF
END_FOR;
onlineCnt := cnt;
(* requestExecuting/Get -- Interface *)
VAR
i: DINT; //Loop variable
END_VAR
(* requestExecuting/Get -- Implementation *)
// ===== Test for any requests executing =====
requestExecuting := activeIndex <> -1;
(* toString/Get -- Interface *)
(* toString/Get -- Implementation *)
//Calculation for tostring
IF onlineCnt = requestCnt THEN
toString := atMbClientToString.allOk;
ELSIF onlineCnt = 0 THEN
toString := atMbClientToString.noneOk;
ELSE
toString := atMbClientToString.someOk;
END_IF
(* mbsClient/Get -- Interface *)
VAR
END_VAR
(* mbsClient/Get -- Implementation *)
IF NOT __ISVALIDREF(client) OR_ELSE NOT __ISVALIDREF(client.client) THEN
RETURN;
END_IF
mbsClient REF= client.client;
(* replyTimeout/Get -- Interface *)
VAR
END_VAR
(* replyTimeout/Get -- Implementation *)
IF in_timeout > 0 THEN
//Explicitly set timout
replyTimeout := ANY_TO_UDINT((in_timeout) * 1000);
ELSE
// If client timeout is set, use that.
IF client.in_timeout <> T#0MS THEN
replyTimeout := TIME_TO_UDINT(client.in_timeout) * 1000;
ELSIF client.dataRate <> 0 THEN
//Data transmision time * 1.5 to get the timeout. Result is in ms
//8 byte request, then 255 byte reply = 2630 bits.
replyTimeout := REAL_TO_UDINT(2630 / DINT_TO_REAL(client.dataRate) * 1.5 * 1000000);
ELSE
//In case client datarate = zero
replyTimeout := 1000000; //1 second?
END_IF
END_IF
(* execute/Get -- Interface *)
VAR
END_VAR
(* execute/Get -- Implementation *)
execute := _execute;
(* execute/Set -- Interface *)
VAR
END_VAR
(* execute/Set -- Implementation *)
IF reqDone OR reqError THEN
_execute := FALSE;
RETURN;
END_IF
_execute := execute;
(* reqBusy/Get -- Interface *)
VAR
END_VAR
(* reqBusy/Get -- Implementation *)
reqBusy := _reqBusy;
(* reqBusy/Set -- Interface *)
VAR
END_VAR
(* reqBusy/Set -- Implementation *)
_reqBusy := reqBusy;
(* reqDone/Get -- Interface *)
VAR
END_VAR
(* reqDone/Get -- Implementation *)
reqDone := _reqDone;
(* reqDone/Set -- Interface *)
VAR
END_VAR
(* reqDone/Set -- Implementation *)
_reqDone := reqDone;
IF (execute AND reqDone) THEN
// ===== Request has just completed succesfully =====
//Update last valid duration
lastValidDuration := ULINT_TO_UDINT((TSSus - lastReqStart)/1000);
IF lastValidDuration > longestValidDuration THEN
longestValidDuration := lastValidDuration;
END_IF
//Counters
doneCnt := doneCnt + 1;
doneCntRst := doneCntRst + 1;
errorCntRst := 0;
//Update error info
lastErrorId := modbusFB.Error.OK;
lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS;
execute := FALSE;
setToString();
END_IF
(* reqError/Get -- Interface *)
VAR
END_VAR
(* reqError/Get -- Implementation *)
reqError := _reqError;
(* reqError/Set -- Interface *)
VAR
END_VAR
(* reqError/Set -- Implementation *)
_reqError := reqError;
IF (execute AND reqError) THEN
// ===== Request has just completed with error =====
// Counters
errorCnt := errorCnt + 1;
errorCntRst := errorCntRst + 1;
doneCntRst := 0;
//Turn off execute
execute := FALSE;
//Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error.
IF lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN
timeoutCnt := timeoutCnt + 1;
lastExceptionCode := atMBExceptionCodes.TIMEOUT;
END_IF
setToString();
END_IF
(* atMbClient/nativeCall -- Interface *)
METHOD nativeCall : UDINT
VAR_INPUT
command : UDINT;
END_VAR
(* atMbClient/nativeCall -- Implementation *)
nativeCall := CmpAtomModbus.atMbCall_cext(
command := command, handle := nativeHandle, ipAddress := nativeIP,
port := nativePort, unitId := nativeArgUnit, functionCode := nativeArgFunction,
startAddress := nativeArgAddress, quantity := nativeArgQuantity,
pData := ADR(nativeIO[0]), dataCapacity := 2000,
pStatus := ADR(nativeStatus[0]), statusCapacity := 24, nowMs := nativeNowMs,
connectTimeoutMs := nativeConnectTimeout, responseTimeoutMs := nativeArgTimeout,
retryDelayMs := nativeRetryDelay);
(* atMbClient/nativeCycle -- Interface *)
METHOD nativeCycle
VAR
j : DINT;
best : DINT;
priority : INT;
age : UDINT;
rc : UDINT;
END_VAR
(* atMbClient/nativeCycle -- Implementation *)
SysTimeGetUs(nativeNowUs);
nativeNowMs := nativeNowUs / 1000;
IF in_resetStats THEN
resetStats();
in_resetStats := FALSE;
END_IF;
IF nativeHandle = 0 AND nativeEnable THEN
rc := nativeCall(1);
IF rc <> 0 THEN
out_lastNativeError := rc;
RETURN;
END_IF;
nativeHandle := nativeStatus[0];
nativeArgTimeout := 1000;
rc := nativeCall(9);
IF rc <> 0 THEN
out_lastNativeError := rc;
nativeCall(2); nativeHandle := 0;
RETURN;
END_IF;
nativeCall(3);
END_IF;
IF nativeHandle = 0 THEN
out_connected := FALSE;
out_connectionState := 0;
RETURN;
END_IF;
rc := nativeCall(5);
IF rc = 256 THEN
nativeHandle := 0;
IF nativeActiveRequest <> 0 THEN
nativeActiveRequest^.nativeFinish(10);
END_IF;
nativeActiveRequest := 0; nativeActiveIndex := -1;
RETURN;
ELSIF rc <> 0 THEN
out_lastNativeError := rc;
RETURN;
END_IF;
out_connectionState := nativeStatus[1];
out_connected := (nativeStatus[1] >= 3) AND (nativeStatus[1] <= 5);
out_lastNativeError := nativeStatus[6];
out_socketError := nativeStatus[7];
out_reconnectCount := nativeStatus[11];
IF nativePrevConnected AND NOT out_connected THEN
setAllRequestsOffline();
END_IF;
nativePrevConnected := out_connected;
nativeFinishedIndex := -1;
nativeResultReady := (nativeActiveRequest <> 0) AND
((nativeStatus[3] <> 0) OR (nativeStatus[4] <> 0));
IF nativeResultReady AND (nativeStatus[3] <> 0) AND (nativeStatus[18] <= 4) THEN
rc := nativeCall(7);
IF rc <> 0 THEN
nativeStatus[3] := 0;
nativeStatus[4] := 1;
nativeStatus[5] := rc;
END_IF;
END_IF;
processAllRequests();
IF nativeResultReady THEN
nativeFinishedIndex := nativeActiveIndex;
nativeCall(10);
nativeActiveRequest := 0; nativeActiveIndex := -1;
nativeSelected := 0; nativeResultReady := FALSE;
END_IF;
IF nativeEnable AND nativeStatus[1] = 3 AND nativeActiveRequest = 0 THEN
best := -1; priority := 32767; age := 0;
FOR j := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[j] <> 0 AND_THEN requests[j]^.stats.active
AND NOT requests[j]^.nativeWriteHold AND j <> nativeFinishedIndex THEN
IF requests[j]^.stats.sinceLastExec >= TIME_TO_UDINT(requests[j]^.in_minDelay) THEN
IF (requests[j]^.in_priorityCurrent < priority) OR
((requests[j]^.in_priorityCurrent = priority) AND
(requests[j]^.stats.sinceLastExec > age)) THEN
best := j; priority := requests[j]^.in_priorityCurrent;
age := requests[j]^.stats.sinceLastExec;
END_IF;
END_IF;
END_IF;
END_FOR;
IF best >= 0 THEN
IF priority > 0 THEN
FOR j := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[j]^.in_priorityCurrent > 0 THEN
requests[j]^.in_priorityCurrent := requests[j]^.in_priorityCurrent - 1;
END_IF;
END_FOR;
END_IF;
nativeSelected := requests[best]; nativeActiveIndex := best;
nativeSelected^.stats.execute := TRUE;
nativeSelected^.processRequest();
IF nativeActiveRequest = 0 THEN
nativeActiveIndex := -1; nativeSelected := 0;
END_IF;
END_IF;
END_IF;
allRequestsOnline := (activeReqCnt > 0) AND (onlineCnt = activeReqCnt);
allRequestsOffline := (activeReqCnt > 0) AND (onlineCnt = 0);
numMsgSent := nativeSent; numMsgReply := nativeReplied;
FOR j := 0 TO UINT_TO_DINT(requestCnt)-1 DO
reqStats[j] := ADR(requests[j]^.stats);
END_FOR;
IF nativeWindowStart = 0 THEN nativeWindowStart := nativeNowMs; END_IF;
IF nativeNowMs - nativeWindowStart >= 1000 THEN
msgLastSecondCnt := nativeWindowCount; msgLastSecondTime := nativeWindowTime;
nativeWindowCount := 0; nativeWindowTime := 0; nativeWindowStart := nativeNowMs;
END_IF;
(* atMbClientTCP/nativeRelease -- Interface *)
METHOD nativeRelease : BOOL
VAR
j : DINT;
END_VAR
(* atMbClientTCP/nativeRelease -- Implementation *)
IF nativeHandle <> 0 THEN
nativeCall(2);
IF nativeActiveRequest <> 0 THEN
nativeActiveRequest^.nativeFinish(10);
END_IF;
nativeHandle := 0;
END_IF;
nativeActiveRequest := 0; nativeSelected := 0; nativeActiveIndex := -1;
nativeResultReady := FALSE;
out_connected := FALSE; out_connectionState := 0;
setAllRequestsOffline();
FOR j := 0 TO UINT_TO_DINT(requestCnt)-1 DO
requests[j]^.invalidateNativeData();
END_FOR;
nativeRelease := TRUE;
(* atMbClientTCP/release -- Interface *)
METHOD release : BOOL
(* atMbClientTCP/release -- Implementation *)
in_enable := FALSE;
release := nativeRelease();
(* atMbClientTCP/FB_exit -- Interface *)
METHOD FB_exit : BOOL
VAR_INPUT
bInCopyCode : BOOL;
END_VAR
(* atMbClientTCP/FB_exit -- Implementation *)
FB_exit := nativeRelease();
(* atMbClientTCP/FB_reinit -- Interface *)
METHOD FB_reinit : BOOL
VAR
j : DINT;
END_VAR
(* atMbClientTCP/FB_reinit -- Implementation *)
nativeHandle := 0;
nativeActiveRequest := 0; nativeSelected := 0; nativeActiveIndex := -1;
nativeResultReady := FALSE; nativePrevConnected := FALSE;
requestCnt := 0; out_registrationFull := FALSE;
FOR j := 0 TO 99 DO requests[j] := 0; reqStats[j] := 0; END_FOR;
FB_reinit := TRUE;
(* atMbReqBase/invalidateNativeData -- Interface *)
METHOD PUBLIC invalidateNativeData
(* atMbReqBase/invalidateNativeData -- Implementation *)
out_validData := FALSE;
out_stale := TRUE;
(* atMbReqBase/nativeFinish -- Interface *)
METHOD nativeFinish
VAR_INPUT
result : UDINT;
END_VAR
VAR
mapped : ModbusFB.Error;
ex : ModbusFB.ExceptionCodes;
duration : UDINT;
END_VAR
(* atMbReqBase/nativeFinish -- Implementation *)
out_nativeError := result;
out_socketError := client.nativeStatus[7];
out_writeUncertain := nativeInFlight AND (client.nativeStatus[9] <> 0);
IF (result = 10) AND nativeInFlight AND (nativeFunction >= 15) THEN
out_writeUncertain := TRUE;
END_IF;
IF out_writeUncertain AND client.nativeHoldUncertainWrites THEN
nativeWriteHold := TRUE;
END_IF;
stats.reqBusy := FALSE;
IF result = 0 THEN
duration := ULINT_TO_UDINT(client.nativeNowMs - nativeStartMs);
stats.lastValidDuration := duration;
IF duration > stats.longestValidDuration THEN stats.longestValidDuration := duration; END_IF;
client.nativeReplied := client.nativeReplied + 1;
client.nativeWindowTime := client.nativeWindowTime + duration;
stats.nativeResponseSeen := TRUE;
IF nativeFunction <= 4 THEN
nativeReadComplete := TRUE;
stats.nativeLastSuccessMs := client.nativeNowMs;
out_validData := TRUE;
END_IF;
done(TRUE);
ELSE
CASE result OF
1,258: mapped := ModbusFB.Error.RequestParameterError;
4,7: mapped := ModbusFB.Error.TcpSocketError;
5: mapped := ModbusFB.Error.TcpConnectTimeout;
6: mapped := ModbusFB.Error.ReplyTimeout;
8: mapped := ModbusFB.Error.InvalidReply;
9: mapped := ModbusFB.Error.RequestException;
10: mapped := ModbusFB.Error.RequestCancelled;
ELSE mapped := ModbusFB.Error.InternalError;
END_CASE;
ex := ModbusFB.ExceptionCodes.RESPONSE_SUCCESS;
IF result = 9 THEN
CASE client.nativeStatus[8] OF
1: ex := ModbusFB.ExceptionCodes.ILLEGAL_FUNCTION;
2: ex := ModbusFB.ExceptionCodes.ILLEGAL_DATA_ADDRESS;
3: ex := ModbusFB.ExceptionCodes.ILLEGAL_DATA_VALUE;
4: ex := ModbusFB.ExceptionCodes.SLAVE_DEVICE_FAILURE;
5: ex := ModbusFB.ExceptionCodes.ACKNOWLEDGE;
6: ex := ModbusFB.ExceptionCodes.SLAVE_DEVICE_BUSY;
8: ex := ModbusFB.ExceptionCodes.MEMORY_PARITY_ERROR;
10: ex := ModbusFB.ExceptionCodes.GATEWAY_PATH_UNAVAILABLE;
11: ex := ModbusFB.ExceptionCodes.GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND;
END_CASE;
client.nativeReplied := client.nativeReplied + 1;
stats.nativeResponseSeen := TRUE;
ELSE
stats.nativeResponseSeen := FALSE;
END_IF;
err(in := TRUE,errorId := mapped,exceptionCode := ex);
END_IF;
nativeInFlight := FALSE;
stats();
(* atMbReqBase/nativeProcess -- Interface *)
METHOD nativeProcess
VAR_INPUT
functionCode : BYTE;
END_VAR
(* atMbReqBase/nativeProcess -- Implementation *)
nativeReadComplete := FALSE;
stats.reqDone := FALSE; stats.reqError := FALSE;
stats();
IF NOT client.out_connected THEN stats.setClientOffline(); END_IF;
IF client.nativeActiveRequest = THIS AND client.nativeResultReady THEN
IF (client.nativeStatus[3] <> 0) AND (nativeFunction <= 4) AND NOT nativeCopyAllowed THEN
nativeFinish(1);
ELSE
nativeFinish(client.nativeStatus[5]);
END_IF;
RETURN;
END_IF;
IF stats.execute AND NOT nativeInFlight AND client.nativeSelected = THIS THEN
nativeFunction := functionCode;
nativeCount := stats.reqLength; nativeOffset := in_dataOffset;
nativeStartMs := client.nativeNowMs;
IF out_parameterError OR nativeCount = 0 THEN nativeFinish(1); RETURN; END_IF;
client.nativeArgUnit := in_unitId;
client.nativeArgFunction := functionCode;
client.nativeArgAddress := in_startAddr;
client.nativeArgQuantity := nativeCount;
client.nativeArgTimeout := MAX(UDINT#1,(replyTimeout + 999)/1000);
nativeRC := client.nativeCall(11);
IF nativeRC = 0 THEN nativeRC := client.nativeCall(6); END_IF;
IF nativeRC = 0 THEN
client.nativeActiveRequest := THIS;
nativeInFlight := TRUE;
stats.reqBusy := TRUE;
client.nativeSent := client.nativeSent + 1;
client.nativeWindowCount := client.nativeWindowCount + 1;
ELSE nativeFinish(nativeRC);
END_IF;
stats();
END_IF;
IF out_validData THEN
out_dataAgeMs := client.nativeNowMs - stats.nativeLastSuccessMs;
out_stale := out_dataAgeMs > TIME_TO_UDINT(in_staleAfter);
ELSE out_stale := TRUE;
END_IF;
(* atMbReqBase/FB_reinit -- Interface *)
METHOD FB_reinit : BOOL
(* atMbReqBase/FB_reinit -- Implementation *)
nativeInFlight := FALSE;
stats.execute := FALSE;
stats.reqBusy := FALSE;
stats.reqDone := FALSE;
stats.reqError := FALSE;
FB_reinit := TRUE;