Skip to content

Commit 74f4fb9

Browse files
committed
refactor: Refactored the way var-length strings are handled to reduce the number of requests sent to the PLC.
1 parent d6fa6f2 commit 74f4fb9

File tree

4 files changed

+122
-71
lines changed

4 files changed

+122
-71
lines changed

plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/optimizer/S7Optimizer.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,23 @@ protected List<PlcReadRequest> processReadRequest(PlcReadRequest readRequest, Dr
8989
continue;
9090
}
9191

92-
// Var length strings go into their own separate read-request.
93-
if ((readRequest.getTag(tagName) instanceof S7StringVarLengthTag)) {
94-
LinkedHashMap<String, PlcTag> strTags = new LinkedHashMap<>();
95-
strTags.put(tagName, readRequest.getTag(tagName));
96-
processedRequests.add(new DefaultPlcReadRequest(
97-
((DefaultPlcReadRequest) readRequest).getReader(), strTags));
98-
continue;
99-
}
100-
10192
////////////////////////////////////////////////////////////////////////////////////////////////////////////
10293
// Processing of normal tags.
10394
////////////////////////////////////////////////////////////////////////////////////////////////////////////
10495
S7Tag tag = (S7Tag) readRequest.getTag(tagName);
10596

10697
int readRequestItemSize = S7_ADDRESS_ANY_SIZE;
107-
int readResponseItemSize = 4 + (tag.getNumberOfElements() * tag.getDataType().getSizeInBytes());
98+
// If we're reading var-length strings, then we'll read the sizes of the strings instead
99+
// and the S7ProtocolLogic will handle reading the actual strings in an additional request.
100+
int readResponseItemElementSize = tag.getDataType().getSizeInBytes();
101+
if(readRequest.getTag(tagName) instanceof S7StringVarLengthTag) {
102+
if(((S7StringVarLengthTag) readRequest.getTag(tagName)).getDataType() == TransportSize.STRING) {
103+
readResponseItemElementSize = 2;
104+
} else if(((S7StringVarLengthTag) readRequest.getTag(tagName)).getDataType() == TransportSize.WSTRING) {
105+
readResponseItemElementSize = 4;
106+
}
107+
}
108+
int readResponseItemSize = 4 + (tag.getNumberOfElements() * readResponseItemElementSize);
108109
// If it's an odd number of bytes, add one to make it even
109110
if (readResponseItemSize % 2 == 1) {
110111
readResponseItemSize++;
@@ -169,7 +170,7 @@ else if (EMPTY_READ_RESPONSE_SIZE + readResponseItemSize > s7DriverContext.getPd
169170
return processedRequests;
170171
}
171172

172-
protected PlcReadResponse processReadResponses(PlcReadRequest readRequest, Map<PlcReadRequest, SubResponse<PlcReadResponse>> readResponses) {
173+
protected PlcReadResponse processReadResponses(PlcReadRequest readRequest, Map<PlcReadRequest, SubResponse<PlcReadResponse>> readResponses, DriverContext driverContext) {
173174
Map<String, ResponseItem<PlcValue>> tagValues = new HashMap<>();
174175
for (Map.Entry<PlcReadRequest, SubResponse<PlcReadResponse>> requestsEntries : readResponses.entrySet()) {
175176
PlcReadRequest curRequest = requestsEntries.getKey();

plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/S7ProtocolLogic.java

Lines changed: 108 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
import org.apache.plc4x.java.s7.events.S7UserEvent;
7171
import org.apache.plc4x.java.s7.readwrite.utils.S7PlcSubscriptionRequest;
7272

73+
import javax.xml.crypto.Data;
74+
7375
/**
7476
* The S7 Protocol states that there can not be more then {min(maxAmqCaller, maxAmqCallee} "ongoing" requests.
7577
* So we need to limit those.
@@ -652,9 +654,9 @@ private PlcSubscriptionResponse decodeEventSubscriptionResponse(String strTagNam
652654
}
653655
// This seems to be the case if we're trying to do a subscription on a device that doesn't support that.
654656
else if((errorClass == 0) && (errorCode == (short) 0x8104)) {
655-
logger.warn("Got an error response from the PLC. This particular response code usually indicates " +
657+
logger.warn("Got an error response from the PLC. Error Class: {}, Error Code {}. This particular response code usually indicates " +
656658
"that a given service is not implemented on the PLC. Most probably you tried to subscribe to " +
657-
"data on a PLC that doesn't support subsciptions (S7-1200 or S7-1500)",
659+
"data on a PLC that doesn't support subscriptions (S7-1200 or S7-1500)",
658660
errorClass, errorCode);
659661
for (String tagName : plcSubscriptionRequest.getTagNames()) {
660662
values.put(tagName, new ResponseItem<>(PlcResponseCode.UNSUPPORTED, null));
@@ -1287,42 +1289,82 @@ private CompletableFuture<S7Message> performClkSetRequest(DefaultPlcWriteRequest
12871289
private CompletableFuture<S7Message> performVarLengthStringReadRequest(DefaultPlcReadRequest request) {
12881290
CompletableFuture<S7Message> future = new CompletableFuture<>();
12891291

1290-
// Resolve the lengths of all var-length string fields in the request.
1291-
CompletableFuture<Map<S7StringVarLengthTag, StringSizes>> stringSizesFuture = getStringSizes(request);
1292-
stringSizesFuture.whenComplete((s7StringVarLengthTagStringSizesMap, throwable) -> {
1293-
if (throwable != null) {
1294-
future.completeExceptionally(new PlcProtocolException("Error resolving string sizes", throwable));
1295-
} else {
1296-
// Create an alternative list of request items, where all var-length string tags are replaced with
1297-
// fixed-length string tags using the string length returned by the previous request.
1298-
LinkedHashMap<String, PlcTag> updatedRequestItems = new LinkedHashMap<>(request.getNumberOfTags());
1299-
for (String tagName : request.getTagNames()) {
1300-
PlcTag tag = request.getTag(tagName);
1301-
if (tag instanceof S7StringVarLengthTag) {
1302-
S7StringVarLengthTag varLengthTag = (S7StringVarLengthTag) tag;
1303-
int stringLength = s7StringVarLengthTagStringSizesMap.get(varLengthTag).getCurLength();
1304-
S7StringFixedLengthTag newTag = new S7StringFixedLengthTag(varLengthTag.getDataType(), varLengthTag.getMemoryArea(),
1305-
varLengthTag.getBlockNumber(), varLengthTag.getByteOffset(), varLengthTag.getBitOffset(),
1306-
varLengthTag.getNumberOfElements(), stringLength);
1307-
updatedRequestItems.put(tagName, newTag);
1308-
} else {
1309-
updatedRequestItems.put(tagName, tag);
1310-
}
1292+
// Replace the var-length string fields with requests to read the
1293+
// length information instead of the string content.
1294+
int numVarLengthStrings = 0;
1295+
LinkedHashMap<String, PlcTag> updatedRequestItems = new LinkedHashMap<>(request.getNumberOfTags());
1296+
for (String tagName : request.getTagNames()) {
1297+
S7Tag s7tag = (S7Tag) request.getTag(tagName);
1298+
if(s7tag instanceof S7StringVarLengthTag) {
1299+
if(s7tag.getDataType() == TransportSize.STRING) {
1300+
updatedRequestItems.put(tagName, new S7Tag(TransportSize.BYTE, s7tag.getMemoryArea(), s7tag.getBlockNumber(), s7tag.getByteOffset(), s7tag.getBitOffset(), 2));
1301+
numVarLengthStrings++;
1302+
} else if(s7tag.getDataType() == TransportSize.WSTRING) {
1303+
updatedRequestItems.put(tagName, new S7Tag(TransportSize.BYTE, s7tag.getMemoryArea(), s7tag.getBlockNumber(), s7tag.getByteOffset(), s7tag.getBitOffset(), 4));
1304+
numVarLengthStrings++;
13111305
}
1306+
} else {
1307+
updatedRequestItems.put(tagName, s7tag);
1308+
}
1309+
}
13121310

1313-
// Use the normal functionality to execute the read request.
1314-
// TODO: Here technically the request object in the response will not match the original request.
1315-
CompletableFuture<S7Message> s7MessageCompletableFuture = performOrdinaryReadRequest(new DefaultPlcReadRequest(request.getReader(), updatedRequestItems));
1316-
s7MessageCompletableFuture.whenComplete((s7Message, throwable1) -> {
1317-
if (throwable1 != null) {
1318-
future.completeExceptionally(throwable1);
1319-
} else {
1320-
future.complete(s7Message);
1311+
CompletableFuture<S7Message> s7MessageCompletableFuture = performOrdinaryReadRequest(new DefaultPlcReadRequest(request.getReader(), updatedRequestItems));
1312+
int finalNumVarLengthStrings = numVarLengthStrings;
1313+
s7MessageCompletableFuture.whenComplete((s7Message, throwable1) -> {
1314+
if (throwable1 != null) {
1315+
future.completeExceptionally(throwable1);
1316+
return;
1317+
}
1318+
// Collect the responses for the var-length strings and read them separately.
1319+
LinkedHashMap<String, PlcTag> varLengthStringTags = new LinkedHashMap<>(finalNumVarLengthStrings);
1320+
int curItem = 0;
1321+
for (String tagName : request.getTagNames()) {
1322+
S7Tag s7tag = (S7Tag) request.getTag(tagName);
1323+
if(s7tag instanceof S7StringVarLengthTag) {
1324+
S7VarPayloadDataItem s7VarPayloadDataItem = ((S7PayloadReadVarResponse) s7Message.getPayload()).getItems().get(curItem);
1325+
// Simply ignore processing var-lenght strings that are not ok
1326+
if(s7VarPayloadDataItem.getReturnCode() == DataTransportErrorCode.OK) {
1327+
ReadBuffer rb = new ReadBufferByteBased(s7VarPayloadDataItem.getData());
1328+
try {
1329+
if (s7tag.getDataType() == TransportSize.STRING) {
1330+
rb.readShort(8);
1331+
int stringLength = rb.readShort(8);
1332+
varLengthStringTags.put(tagName, new S7StringFixedLengthTag(TransportSize.STRING, s7tag.getMemoryArea(), s7tag.getBlockNumber(), s7tag.getByteOffset(), s7tag.getBitOffset(), 1, stringLength));
1333+
} else if (s7tag.getDataType() == TransportSize.WSTRING) {
1334+
rb.readInt(16);
1335+
int stringLength = rb.readInt(16);
1336+
varLengthStringTags.put(tagName, new S7StringFixedLengthTag(TransportSize.WSTRING, s7tag.getMemoryArea(), s7tag.getBlockNumber(), s7tag.getByteOffset(), s7tag.getBitOffset(), 1, stringLength));
1337+
}
1338+
} catch (Exception e) {
1339+
logger.warn("Error parsing string size for tag {}", tagName, e);
1340+
}
13211341
}
1322-
});
1342+
}
1343+
curItem++;
13231344
}
1345+
// TODO: Technically we would need to let this go through the optimizer in order to split things up.
1346+
// For this we need access to the PlcReader instance of this driver
1347+
CompletableFuture<S7Message> readStringsCompletableFuture = performOrdinaryReadRequest(new DefaultPlcReadRequest(request.getReader(), varLengthStringTags));
1348+
readStringsCompletableFuture.whenComplete((s7StringMessage, throwable2) -> {
1349+
// Build a new S7Message that replaces the var-length string items of the previous request with the responses of this response.
1350+
int curInitialItem = 0;
1351+
int curVarLengthStringItem = 0;
1352+
List<S7VarPayloadDataItem> varLengthStringItems = new ArrayList<>(request.getNumberOfTags());
1353+
for (String tagName : request.getTagNames()) {
1354+
S7Tag s7tag = (S7Tag) request.getTag(tagName);
1355+
S7VarPayloadDataItem curResultItem = ((S7PayloadReadVarResponse) s7Message.getPayload()).getItems().get(curInitialItem);
1356+
if(s7tag instanceof S7StringVarLengthTag) {
1357+
if(curResultItem.getReturnCode() == DataTransportErrorCode.OK) {
1358+
curResultItem = ((S7PayloadReadVarResponse) s7StringMessage.getPayload()).getItems().get(curVarLengthStringItem);
1359+
curVarLengthStringItem++;
1360+
}
1361+
}
1362+
varLengthStringItems.add(curResultItem);
1363+
curInitialItem++;
1364+
}
1365+
future.complete(new S7MessageResponse(s7Message.getTpduReference(), s7Message.getParameter(), new S7PayloadReadVarResponse(varLengthStringItems), (short) 0, (short) 0));
1366+
});
13241367
});
1325-
13261368
return future;
13271369
}
13281370

@@ -1478,18 +1520,15 @@ private CompletableFuture<S7Message> sendInternal(S7Message request) {
14781520
* This method is called when there is no handler for the message.
14791521
* By default it must correspond to asynchronous events, which if so,
14801522
* must be transferred to the event queue.
1481-
*
14821523
* The event's own information is encapsulated in the parameters and payload
14831524
* field. From this it is abstracted to the corresponding event model.
1484-
*
14851525
* 01. S7ModeEvent:
14861526
* 02. S7UserEvent:
14871527
* 03. S7SysEvent:
14881528
* 04. S7AlarmEvent
14891529
* 05. S7CyclicEvent:
14901530
* 06. S7CyclicEvent:
1491-
*
1492-
* TODO: Use mspec to generate types that allow better interpretation of
1531+
* TODO: Use mspec to generate types that allow better interpretation of
14931532
* the code using "instanceof".
14941533
*/
14951534
@Override
@@ -1563,7 +1602,7 @@ protected void decode(ConversationContext<TPKTPacket> context, TPKTPacket msg) t
15631602

15641603
if (cycChangeValueEvents.containsKey(parameterItem.getSequenceNumber())){
15651604
S7CyclicEvent lastCycEvent = cycChangeValueEvents.get(parameterItem.getSequenceNumber());
1566-
if (cycEvent.equals(lastCycEvent ) == false) {
1605+
if (!cycEvent.equals(lastCycEvent)) {
15671606
cycChangeValueEvents.replace(parameterItem.getSequenceNumber(), cycEvent);
15681607
eventQueue.add(cycEvent);
15691608
}
@@ -1962,25 +2001,29 @@ private S7VarPayloadDataItem serializePlcValue(S7Tag tag, PlcValue plcValue) {
19622001
DataTransportSize transportSize = tag.getDataType().getDataTransportSize();
19632002
int stringLength = (tag instanceof S7StringFixedLengthTag) ? ((S7StringFixedLengthTag) tag).getStringLength() : 254;
19642003
ByteBuffer byteBuffer = null;
1965-
for (int i = 0; i < tag.getNumberOfElements(); i++) {
1966-
int lengthInBits = DataItem.getLengthInBits(plcValue.getIndex(i), tag.getDataType().getDataProtocolId(), s7DriverContext.getControllerType(), stringLength);
1967-
1968-
// Cap the length of the string with the maximum allowed size.
1969-
if (tag.getDataType() == TransportSize.STRING) {
1970-
lengthInBits = Math.min(lengthInBits, (stringLength * 8) + 16);
1971-
} else if (tag.getDataType() == TransportSize.WSTRING) {
1972-
lengthInBits = Math.min(lengthInBits, (stringLength * 16) + 32);
1973-
} else if (tag.getDataType() == TransportSize.S5TIME) {
1974-
lengthInBits = lengthInBits * 8;
1975-
}
1976-
final WriteBufferByteBased writeBuffer = new WriteBufferByteBased((int) Math.ceil(((float) lengthInBits) / 8.0f));
1977-
DataItem.staticSerialize(writeBuffer, plcValue.getIndex(i), tag.getDataType().getDataProtocolId(), s7DriverContext.getControllerType(), stringLength);
1978-
// Allocate enough space for all items.
1979-
if (byteBuffer == null) {
1980-
// TODO: This logic will cause problems when reading arrays of strings.
1981-
byteBuffer = ByteBuffer.allocate(writeBuffer.getBytes().length * tag.getNumberOfElements());
2004+
if((tag.getDataType() == TransportSize.BYTE) && (tag.getNumberOfElements() > 1)) {
2005+
byteBuffer = ByteBuffer.allocate(tag.getNumberOfElements());
2006+
byteBuffer.put(plcValue.getRaw());
2007+
} else {
2008+
for (int i = 0; i < tag.getNumberOfElements(); i++) {
2009+
int lengthInBits = DataItem.getLengthInBits(plcValue.getIndex(i), tag.getDataType().getDataProtocolId(), s7DriverContext.getControllerType(), stringLength);
2010+
// Cap the length of the string with the maximum allowed size.
2011+
if (tag.getDataType() == TransportSize.STRING) {
2012+
lengthInBits = Math.min(lengthInBits, (stringLength * 8) + 16);
2013+
} else if (tag.getDataType() == TransportSize.WSTRING) {
2014+
lengthInBits = Math.min(lengthInBits, (stringLength * 16) + 32);
2015+
} else if (tag.getDataType() == TransportSize.S5TIME) {
2016+
lengthInBits = lengthInBits * 8;
2017+
}
2018+
final WriteBufferByteBased writeBuffer = new WriteBufferByteBased((int) Math.ceil(((float) lengthInBits) / 8.0f));
2019+
DataItem.staticSerialize(writeBuffer, plcValue.getIndex(i), tag.getDataType().getDataProtocolId(), s7DriverContext.getControllerType(), stringLength);
2020+
// Allocate enough space for all items.
2021+
if (byteBuffer == null) {
2022+
// TODO: This logic will cause problems when reading arrays of strings.
2023+
byteBuffer = ByteBuffer.allocate(writeBuffer.getBytes().length * tag.getNumberOfElements());
2024+
}
2025+
byteBuffer.put(writeBuffer.getBytes());
19822026
}
1983-
byteBuffer.put(writeBuffer.getBytes());
19842027
}
19852028
if (byteBuffer != null) {
19862029
byte[] data = byteBuffer.array();
@@ -2040,7 +2083,8 @@ private PlcResponseCode decodeResponseCode(DataTransportErrorCode dataTransportE
20402083
case OK:
20412084
return PlcResponseCode.OK;
20422085
case NOT_FOUND:
2043-
return PlcResponseCode.NOT_FOUND;
2086+
// It seems the S7 devices return NOT_FOUND if for example we try to access a DB number
2087+
// which doesn't exist. In other protocols we all map that to invalid address.
20442088
case INVALID_ADDRESS:
20452089
return PlcResponseCode.INVALID_ADDRESS;
20462090
case DATA_TYPE_NOT_SUPPORTED:
@@ -2309,6 +2353,12 @@ else if (varLengthStringTag.getDataType() == TransportSize.WSTRING) {
23092353
int curItemIndex = 0;
23102354
for (S7StringVarLengthTag varLengthStringTag : varLengthStringTags) {
23112355
S7VarPayloadDataItem s7VarPayloadDataItem = getLengthsResponse.getItems().get(curItemIndex);
2356+
// Something went wrong.
2357+
// We simply don't save the length information, and then we'll treat it as INVALID_ADDRESS later on
2358+
// in the code.
2359+
if(s7VarPayloadDataItem.getReturnCode() != DataTransportErrorCode.OK) {
2360+
continue;
2361+
}
23122362
ReadBufferByteBased readBuffer = new ReadBufferByteBased(s7VarPayloadDataItem.getData());
23132363
try {
23142364
if (varLengthStringTag.getDataType() == TransportSize.STRING) {
@@ -2323,7 +2373,7 @@ else if (varLengthStringTag.getDataType() == TransportSize.WSTRING) {
23232373
throw new PlcInvalidTagException("Only STRING and WSTRING allowed here.");
23242374
}
23252375
} catch (ParseException e) {
2326-
throw new PlcInvalidTagException("Error reading var-length string actual lengths.");
2376+
throw new PlcInvalidTagException("Error parsing var-length string actual lengths.");
23272377
}
23282378
}
23292379

0 commit comments

Comments
 (0)