(* 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;