"""Patch original native CODESYS export; keep existing public declarations/IDs.""" from pathlib import Path import xml.etree.ElementTree as E import copy, uuid ROOT=Path(__file__).resolve().parents[1] r=E.parse(ROOT/'compat/Original_HomeCodesys.export').getroot() listing=r.find('.//List2[@Name="EntryList"]') entries=list(listing) def meta(e,n): return e.find('./Single[@Name="MetaObject"]/Single[@Name="'+n+'"]') names={meta(e,'Guid').text:meta(e,'Name').text for e in entries} def key(e): p=e.find('./Single[@Name="ParentSVNodeGuid"]') return (names.get(p.text,''),meta(e,'Name').text) by={key(e):e for e in entries} def node(parent,name): return by[(parent,name)] def textnode(e,part): return e.find('./Single[@Name="Object"]/Single[@Name="'+part+'"]//Single[@Name="TextBlobForSerialisation"]') def get(parent,name,part='Implementation'): return textnode(node(parent,name),part).text or '' def put(parent,name,body,part='Implementation'): textnode(node(parent,name),part).text=body def append_vars(parent,name,body,kind='VAR'): put(parent,name,get(parent,name,'Interface')+'\n'+kind+'\n'+body+'\nEND_VAR\n','Interface') def method(parent,name,decl,body): e=copy.deepcopy(node('atMbClient','resetStats')) old=meta(e,'Guid').text; new=str(uuid.uuid5(uuid.NAMESPACE_URL,'atom-modbus/'+parent+'/'+name)) pg=meta(node('ModbusMaster',parent),'Guid').text for v in e.iter(): if v.text and old in v.text:v.text=v.text.replace(old,new) meta(e,'Name').text=name; meta(e,'ParentGuid').text=pg e.find('./Single[@Name="ParentSVNodeGuid"]').text=pg path=e.find('./Array[@Name="Path"]'); path[-1].text=parent textnode(e,'Interface').text=decl; textnode(e,'Implementation').text=body listing.append(e); by[(parent,name)]=e append_vars('ModbusMaster','atMbClient','''nativeMode : BOOL; nativeHandle : UDINT; nativeIP : DWORD; nativePort : UINT; nativeNowUs : SYSTIME; nativeNowMs : ULINT; nativeStatus : ARRAY[0..23] OF UDINT; nativeIO : ARRAY[0..1999] OF WORD; nativeActiveRequest : POINTER TO atMbReqBase; nativeActiveIndex : DINT := -1; nativeSelected : POINTER TO atMbReqBase; nativeResultReady : BOOL; nativeFinishedIndex : DINT := -1; nativeArgUnit : BYTE; nativeArgFunction : BYTE; nativeArgAddress : UINT; nativeArgQuantity : UINT; nativeArgTimeout : UDINT := 1000; nativeCallResult : UDINT; nativeSent : UDINT; nativeReplied : UDINT; nativeWindowStart : ULINT; nativeWindowCount : UDINT; nativeWindowTime : UDINT; nativePrevConnected : BOOL; nativeEnable : BOOL := TRUE; nativeConnectTimeout : UDINT := 2000; nativeRetryDelay : UDINT := 1000; nativeHoldUncertainWrites : BOOL := FALSE;''') # CODESYS permits another FB to assign inputs, but not local VAR members. # Match the original stats pattern for the shared scheduler working fields. e=node('ModbusMaster','atMbClient') decl=textnode(e,'Interface').text shared=['nativeActiveRequest','nativeIO','nativeArgUnit','nativeArgFunction', 'nativeArgAddress','nativeArgQuantity','nativeArgTimeout','nativeSent', 'nativeReplied','nativeWindowCount','nativeWindowTime'] lines=decl.splitlines(); moved=[]; kept=[] for line in lines: if any(line.strip().startswith(n+' :') for n in shared): moved.append(line) else: kept.append(line) textnode(e,'Interface').text='\n'.join(kept)+'\nVAR_INPUT\n'+''.join( "{attribute 'symbol' := 'none'}\n"+line+'\n' for line in moved)+'END_VAR\n' put('atMbClient','setAllRequestsOffline', get('atMbClient','setAllRequestsOffline','Interface').replace('METHOD PRIVATE','METHOD PROTECTED'), 'Interface') append_vars('ModbusMaster','atMbClient','''out_connected : BOOL; out_connectionState : UDINT; out_lastNativeError : UDINT; out_socketError : UDINT; out_reconnectCount : UDINT; out_registrationFull : BOOL;''','VAR_OUTPUT') method('atMbClient','nativeCall','''METHOD nativeCall : UDINT VAR_INPUT command : UDINT; END_VAR''','''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);''') method('atMbClient','nativeCycle','''METHOD nativeCycle VAR j : DINT; best : DINT; priority : INT; age : UDINT; rc : UDINT; END_VAR''','''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;''') put('ModbusMaster','atMbClient','''IF nativeMode THEN nativeCycle(); RETURN; END_IF; '''+get('ModbusMaster','atMbClient')) # RTU retains old code and public compatibility methods. put('atMbClient','resetStats','''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; '''+get('atMbClient','resetStats')) put('atMbClient','addRequest','''IF request = 0 THEN RETURN; END_IF; IF requestCnt > 0 THEN FOR i := 0 TO 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;''') put('activeIndex','Get','''IF nativeMode THEN activeIndex := nativeActiveIndex; RETURN; END_IF; '''+get('activeIndex','Get')) # TCP declarations: existing inputs stay unchanged, extra diagnostics inherited. append_vars('ModbusMaster','atMbClientTCP','''in_enable : BOOL := TRUE; in_connectTimeout : TIME := T#2S; in_reconnectDelay : TIME := T#1S; in_holdUncertainWrites : BOOL := FALSE;''','VAR_INPUT') append_vars('ModbusMaster','atMbClientTCP','''nativeEndpointIP : DWORD; nativeEndpointPort : UINT; nativeConfigConnect : UDINT; nativeConfigRetry : UDINT;''') put('ModbusMaster','atMbClientTCP','''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^();''') put('atMbClientTCP','recreateClient','''nativeRelease(); // Next scan creates a fresh native context with current endpoint settings.''') # Remove the old local pointer used by the recreated built-in TCP client. put('atMbClientTCP','recreateClient','METHOD recreateClient\nVAR\nEND_VAR','Interface') method('atMbClientTCP','nativeRelease','''METHOD nativeRelease : BOOL VAR j : DINT; END_VAR''','''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;''') method('atMbClientTCP','release','''METHOD release : BOOL''','''in_enable := FALSE; release := nativeRelease();''') method('atMbClientTCP','FB_exit','''METHOD FB_exit : BOOL VAR_INPUT bInCopyCode : BOOL; END_VAR''','''FB_exit := nativeRelease();''') method('atMbClientTCP','FB_reinit','''METHOD FB_reinit : BOOL VAR j : DINT; END_VAR''','''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;''') append_vars('ModbusMaster','atMbReqBase','''nativeFunction : BYTE; nativeCount : UINT; nativeOffset : UINT; nativeInFlight : BOOL; nativeReadComplete : BOOL; nativeCopyAllowed : BOOL; nativeWriteHold : BOOL; nativeStartMs : ULINT; nativeLoop : DINT; nativeRC : UDINT;''') append_vars('ModbusMaster','atMbReqBase','''out_nativeError : UDINT; out_socketError : UDINT; out_writeUncertain : BOOL; out_validData : BOOL; out_dataAgeMs : ULINT; out_stale : BOOL := TRUE; out_parameterError : BOOL;''','VAR_OUTPUT') append_vars('ModbusMaster','atMbReqBase','''in_staleAfter : TIME := T#2S;''','VAR_INPUT') method('atMbReqBase','invalidateNativeData','''METHOD PUBLIC invalidateNativeData''','''out_validData := FALSE; out_stale := TRUE;''') method('atMbReqBase','nativeFinish','''METHOD nativeFinish VAR_INPUT result : UDINT; END_VAR VAR mapped : ModbusFB.Error; ex : ModbusFB.ExceptionCodes; duration : UDINT; END_VAR''','''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();''') method('atMbReqBase','nativeProcess','''METHOD nativeProcess VAR_INPUT functionCode : BYTE; END_VAR''','''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;''') # Request body remains registration/clipping; fix remaining array capacity in native mode. for name,f,bit,write in [('atMbReqReadCoil',1,True,False),('atMbReqReadDiscrete',2,True,False),('atMbReqReadHolding',3,False,False),('atMbReqReadInput',4,False,False),('atMbReqWriteCoil',15,True,True),('atMbReqWriteHolding',16,False,True)]: original=get('ModbusMaster',name) put('ModbusMaster',name,'''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; '''+original) native='''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 write: native+=''' 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 ''' native+=(' client.nativeIO[nativeLoop] := BOOL_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]);\n' if bit else ' client.nativeIO[nativeLoop] := UINT_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]);\n') native+=' END_FOR;\n END_IF;\n' native+=f' nativeProcess({f});\n' if not write: native+=''' IF nativeReadComplete AND nativeCopyAllowed THEN FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO ''' native+=(' data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0;\n' if bit else ' data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]);\n') native+=' END_FOR;\n END_IF;\n' native+=' RETURN;\nEND_IF;\n' put(name,'processRequest',native+get(name,'processRequest')) put(name,'recreateRequest','''IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; nativeInFlight := FALSE; recreateRequest := 0; RETURN; END_IF; '''+get(name,'recreateRequest')) if write:put(name,'writeNow','''nativeWriteHold := FALSE; out_writeUncertain := FALSE; '''+get(name,'writeNow')) method('atMbReqBase','FB_reinit','''METHOD FB_reinit : BOOL''','''nativeInFlight := FALSE; stats.execute := FALSE; stats.reqBusy := FALSE; stats.reqDone := FALSE; stats.reqError := FALSE; FB_reinit := TRUE;''') put('atMbClient','setupClient','''IF nativeMode THEN setupClient := nativeHandle <> 0; RETURN; END_IF; '''+get('atMbClient','setupClient')) put('atMbReqBase','setupClient','''IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN setupClient := FALSE; // Native path has no ModbusFB request object. RETURN; END_IF; '''+get('atMbReqBase','setupClient')) # Stats type names remain unchanged; add native health/freshness fields. append_vars('ModbusMaster','atMbRequestStats','''nativeResponseSeen : BOOL; nativeLastSuccessMs : ULINT;''','VAR_INPUT') s=get('ModbusMaster','atMbRequestStats') s=s.replace('IF doneCntRst >=3 THEN','IF nativeResponseSeen OR doneCntRst >=3 THEN') s=s.replace('IF errorCntRst >= 3 THEN','IF NOT nativeResponseSeen AND errorCntRst >= 3 THEN') a=s.index('IF lastReqStart < TSSus - 60000000 THEN');b=s.index('sinceLastExec :=',a) s=s[:a]+'''IF TSSus >= lastReqStart THEN IF TSSus - lastReqStart > 60000000 THEN lastReqStart := TSSus - 60000000; END_IF; ELSE lastReqStart := TSSus; END_IF; '''+s[b:] put('ModbusMaster','atMbRequestStats',s) put('atMbRequestStats','setClientOffline','nativeResponseSeen := FALSE;\n'+get('atMbRequestStats','setClientOffline')) # Dispatch may run before any request FB has registered on the first scan. for name, call in [('processAllRequests', 'processRequest()'), ('recreateAllRequests', 'recreateRequest()'), ('setAllRequestsOffline', 'stats.setClientOffline()')]: decl = get('atMbClient', name, 'Interface') if 'i:' not in decl and 'i :' not in decl: decl += '\nVAR\n i : DINT;\nEND_VAR\n' decl = decl.replace('i: UINT;', 'i: DINT;') put('atMbClient', name, decl, 'Interface') put('atMbClient', name, f'''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]^.{call}; END_IF; END_FOR;''') # Keep subtraction signed in every other registry consumer too. import re for e in listing: t = textnode(e, 'Implementation') if t is not None and t.text and 'requestCnt' in t.text: t.text = re.sub(r'(?