2297 lines
64 KiB
Smalltalk
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;
|