first commit
This commit is contained in:
@@ -0,0 +1,542 @@
|
||||
"""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')
|
||||
Reference in New Issue
Block a user