Files
Codesys-Library-Atom-Modbus/tools/build_compat_export.py
T
2026-09-30 21:17:41 -06:00

543 lines
21 KiB
Python

"""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'(?<!\()\brequestCnt\s*-\s*1',
'UINT_TO_DINT(requestCnt)-1', t.text)
# Export native CODESYS XML, retaining all original objects and metadata IDs.
E.indent(r,space=' ')
E.ElementTree(r).write(ROOT/'compat/AtomModbus-Compatible.export',encoding='utf-8',xml_declaration=False)
# Readable ST per changed/new object for reviewing or manual import fallback.
changed=[]
for e in listing:
p,n=key(e)
for part in ['Interface','Implementation']:
t=textnode(e,part)
if t is not None:
changed.append(f'\n(* {p}/{n} -- {part} *)\n{t.text or ""}\n')
(ROOT/'compat/All_Source_Review.st').write_text(''.join(changed))
print('Wrote compatible native export:',len(listing),'objects')