This is an automated email from the ASF dual-hosted git repository. cdutz pushed a commit to branch fix/opc-ua-fine-tuning in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit f80dc154cbe912798bc3c86c8a807d932ac07bee Author: Christofer Dutz <[email protected]> AuthorDate: Tue Feb 17 22:54:51 2026 +0100 fix: Worked on getting all various types of OPC-UA request working with my Siemens S7 OPC-UA Server. --- .../readwrite/BinaryExtensionObjectWithMask.java | 23 ++++-- .../plc4x/java/opcua/context/Conversation.java | 20 ++--- .../java/opcua/protocol/OpcuaProtocolLogic.java | 92 +++++++++++++++------ .../opcua/readwrite/UnknownExtensionObject.java | 93 ++++++++++++++++++++++ .../java/opcua/readwrite/utils/StaticHelper.java | 36 ++++++++- ....java => ManualOpcUaS71500NewFWDriverTest.java} | 53 ++++++------ .../java/opcua/OpcuaParserSerializerTest.java | 2 +- .../protocol/OpcuaSubscriptionHandleTest.java | 5 +- .../opcua/src/test/resources/logback-test.xml | 2 +- .../apache/plc4x/java/spi/Plc4xNettyWrapper.java | 4 +- .../java/spi/values/LegacyPlcValueHandler.java | 4 + .../generated/protocols/opcua/opc-manual.mspec | 2 +- protocols/opcua/src/main/xslt/opc-manual.xsl | 2 +- 13 files changed, 258 insertions(+), 80 deletions(-) diff --git a/plc4j/drivers/opcua/src/main/generated/org/apache/plc4x/java/opcua/readwrite/BinaryExtensionObjectWithMask.java b/plc4j/drivers/opcua/src/main/generated/org/apache/plc4x/java/opcua/readwrite/BinaryExtensionObjectWithMask.java index c1db6b21db..82fe0a7be8 100644 --- a/plc4j/drivers/opcua/src/main/generated/org/apache/plc4x/java/opcua/readwrite/BinaryExtensionObjectWithMask.java +++ b/plc4j/drivers/opcua/src/main/generated/org/apache/plc4x/java/opcua/readwrite/BinaryExtensionObjectWithMask.java @@ -73,8 +73,13 @@ public class BinaryExtensionObjectWithMask extends ExtensionObjectWithMask imple int bodyLength = (int) ((((getBody()) == (null)) ? 0 : getBody().getLengthInBytes())); writeImplicitField("bodyLength", bodyLength, writeSignedInt(writeBuffer, 32)); - // Simple Field (body) - writeSimpleField("body", body, writeComplex(writeBuffer)); + // Manual Field (body) + writeManualField( + "body", + () -> + org.apache.plc4x.java.opcua.readwrite.utils.StaticHelper.serializeExtensionObjectBody( + writeBuffer, body), + writeBuffer); writeBuffer.popContext("BinaryExtensionObjectWithMask"); } @@ -93,8 +98,8 @@ public class BinaryExtensionObjectWithMask extends ExtensionObjectWithMask imple // Implicit Field (bodyLength) lengthInBits += 32; - // Simple field (body) - lengthInBits += body.getLengthInBits(); + // Manual Field (body) + lengthInBits += (((body) == (null)) ? 0 : body.getLengthInBits()); return lengthInBits; } @@ -109,11 +114,13 @@ public class BinaryExtensionObjectWithMask extends ExtensionObjectWithMask imple int bodyLength = readImplicitField("bodyLength", readSignedInt(readBuffer, 32)); ExtensionObjectDefinition body = - readSimpleField( + readManualField( "body", - readComplex( - () -> ExtensionObjectDefinition.staticParse(readBuffer, (int) (extensionId)), - readBuffer)); + readBuffer, + () -> + (ExtensionObjectDefinition) + (org.apache.plc4x.java.opcua.readwrite.utils.StaticHelper + .parseExtensionObjectBody(readBuffer, extensionId, bodyLength))); readBuffer.closeContext("BinaryExtensionObjectWithMask"); // Create the instance diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/Conversation.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/Conversation.java index a9c9c2b176..f9804a57ca 100644 --- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/Conversation.java +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/Conversation.java @@ -75,7 +75,6 @@ import org.apache.plc4x.java.opcua.security.MessageSecurity; import org.apache.plc4x.java.opcua.security.SecurityPolicy; import org.apache.plc4x.java.spi.ConversationContext; import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext; -import org.apache.plc4x.java.spi.generation.ParseException; import org.apache.plc4x.java.spi.generation.ReadBufferByteBased; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -243,16 +242,14 @@ public class Conversation { .unwrap(OpcuaAPU::getMessage) .check(replyType::isInstance) .unwrap(replyType::cast) - .unwrap(msg -> encryptionHandler.decodeMessage(msg)) + .unwrap(encryptionHandler::decodeMessage) .check(replyType::isInstance) .unwrap(replyType::cast) .check(reply -> requestId == sequenceHeaderExtractor.apply(reply).getRequestId()) .check(reply -> sequenceValidator.test(sequenceHeaderExtractor.apply(reply), future)) .check(msg -> accumulateChunkUntilFinal(chunkStorage, msg.getChunk(), chunkExtractor.apply(msg))) .unwrap(msg -> mergeChunks(chunkStorage, msg, sequenceHeaderExtractor.apply(msg), chunkAssembler)) - .handle(response -> { - future.complete(response); - }); + .handle(future::complete); } else { context.sendToWire(new OpcuaAPU(chunks.get(index))); } @@ -270,7 +267,7 @@ public class Conversation { } private CompletableFuture<Object> submit(ExtensionObjectDefinition requestDefinition) { - Integer requestId = tm.getTransactionIdentifier(); + int requestId = tm.getTransactionIdentifier(); ExpandedNodeId expandedNodeId = new ExpandedNodeId( false, //Namespace Uri Specified @@ -303,7 +300,7 @@ public class Conversation { .unwrap(OpcuaAPU::getMessage) .check(OpcuaMessageResponse.class::isInstance) .unwrap(OpcuaMessageResponse.class::cast) - .unwrap(msg -> encryptionHandler.decodeMessage(msg)) + .unwrap(encryptionHandler::decodeMessage) .check(OpcuaMessageResponse.class::isInstance) .unwrap(OpcuaMessageResponse.class::cast) .check(OpcuaMessageResponse.class::isInstance) @@ -329,14 +326,13 @@ public class Conversation { BinaryPayload binary = (BinaryPayload) message; ReadBufferByteBased buffer = new ReadBufferByteBased(binary.getPayload(), org.apache.plc4x.java.spi.generation.ByteOrder.LITTLE_ENDIAN); extensionObjectBody = ExtensionObject.staticParse(buffer, false).getBody(); - } catch (ParseException e) { + } catch (Exception e) { future.completeExceptionally(e); return; } } - if (extensionObjectBody instanceof ServiceFault) { - ServiceFault fault = (ServiceFault) extensionObjectBody; + if (extensionObjectBody instanceof ServiceFault fault) { // If we write the same data a tag already had, Siemens devices return an error. if (fault.getResponseHeader().getServiceResult().getStatusCode() == OpcuaStatusCode.BadNothingToDo.getValue()) { // TODO: Here we need to fake a WriteResponse. @@ -361,7 +357,7 @@ public class Conversation { return context.sendRequest(new OpcuaAPU(messagePDU)) .onError((req, err) -> future.completeExceptionally(err)) .expectResponse(OpcuaAPU.class, Duration.ofMillis(timeout)) - .onTimeout((e) -> future.completeExceptionally(e)); + .onTimeout(future::completeExceptionally); } private <T> T mergeChunks(ChunkStorage chunkStorage, T source, SequenceHeader sequenceHeader, BiFunction<T, BinaryPayload, T> producer) { @@ -419,7 +415,7 @@ public class Conversation { } static PlcProtocolException toProtocolException(ServiceFault fault) { - if (fault.getResponseHeader() instanceof ResponseHeader) { + if (fault.getResponseHeader() != null) { ResponseHeader responseHeader = (ResponseHeader) fault.getResponseHeader(); long statusCode = responseHeader.getServiceResult().getStatusCode(); String statusName = OpcuaStatusCode.isDefined(statusCode) ? OpcuaStatusCode.enumForValue(statusCode).name() : "<unknown>"; diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java index 9d50a2f922..1beed98139 100644 --- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java @@ -450,7 +450,8 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H List<ExtensionObject> objects = ((VariantExtensionObject) variant).getValue(); List<PlcValue> values = new ArrayList<>(objects.size()); for (ExtensionObject eo : objects) { - values.add(new PlcSTRING(eo.toString())); + PlcValue eoValue = extensionObjectToPlcValue(eo); + values.add(eoValue != null ? eoValue : new PlcSTRING(eo.toString())); } value = structurePlcValues(values, variant); } else if (variant instanceof VariantNodeId) { @@ -489,14 +490,51 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H return value; } + /** + * Tries to convert an ExtensionObject with an unknown body type to a PlcValue. + * Handles vendor-specific types like Siemens TE_DTL (Date_And_Time_Long). + * Returns null if the type is not recognized. + */ + private static PlcValue extensionObjectToPlcValue(ExtensionObject eo) { + ExtensionObjectDefinition body = eo.getBody(); + if (!(body instanceof UnknownExtensionObject)) { + return null; + } + byte[] rawBytes = ((UnknownExtensionObject) body).getBodyBytes(); + String typeId = eo.getTypeId().getNodeId().getIdentifier(); + + // Siemens DTL (Date_And_Time_Long): 12 bytes + // uint16 year, uint8 month, uint8 day, uint8 weekday, + // uint8 hour, uint8 minute, uint8 second, uint32 nanoseconds (LE) + if ("TE_DTL".equals(typeId) && rawBytes.length == 12) { + ByteBuffer buf = ByteBuffer.wrap(rawBytes).order(ByteOrder.LITTLE_ENDIAN); + int year = buf.getShort(0) & 0xFFFF; + int month = buf.get(2) & 0xFF; + int day = buf.get(3) & 0xFF; + // byte 4 = weekday, skip + int hour = buf.get(5) & 0xFF; + int minute = buf.get(6) & 0xFF; + int second = buf.get(7) & 0xFF; + int nanoseconds = buf.getInt(8); + return new PlcDATE_AND_LTIME(LocalDateTime.of(year, month, day, hour, minute, second, nanoseconds)); + } + + return null; + } + /** * Recursively applies a type override to a PlcValue. * For PlcList values, each element is converted individually. * For scalar values, the raw numeric is re-interpreted as the target type. */ private static PlcValue applyTypeOverride(PlcValue value, PlcValueType targetType) { - if (value instanceof PlcList) { - PlcList list = (PlcList) value; + // If the value is already a properly typed temporal value (e.g., from DTL + // conversion), skip the override to avoid corrupting it. + PlcValueType currentType = value.getPlcValueType(); + if (currentType == targetType || isTemporalType(currentType)) { + return value; + } + if (value instanceof PlcList list) { List<PlcValue> converted = new ArrayList<>(list.getLength()); for (PlcValue item : list.getList()) { converted.add(applyTypeOverride(item, targetType)); @@ -504,34 +542,38 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H return new PlcList(converted); } long raw = value.getLong(); - switch (targetType) { - case TIME: - return new PlcTIME(raw); - case LTIME: - return new PlcLTIME(raw); - case DATE: + return switch (targetType) { + case TIME -> new PlcTIME(raw); + case LTIME -> new PlcLTIME(raw); + case DATE -> // S7/IEC value is days since 1990-01-01, PlcDATE expects days since 1970-01-01 - return new PlcDATE(raw + IEC_DATE_EPOCH_OFFSET_DAYS); - case LDATE: + new PlcDATE(raw + IEC_DATE_EPOCH_OFFSET_DAYS); + case LDATE -> // PlcLDATE expects seconds since 1970-01-01 - return new PlcLDATE(raw + IEC_DATE_EPOCH_OFFSET_DAYS * 86400L); - case TIME_OF_DAY: + new PlcLDATE(raw + IEC_DATE_EPOCH_OFFSET_DAYS * 86400L); + case TIME_OF_DAY -> // S7/IEC value is milliseconds since midnight - return new PlcTIME_OF_DAY(LocalTime.ofNanoOfDay(raw * 1_000_000L)); - case LTIME_OF_DAY: - return new PlcLTIME_OF_DAY(raw); - case DATE_AND_TIME: + new PlcTIME_OF_DAY(LocalTime.ofNanoOfDay(raw * 1_000_000L)); + case LTIME_OF_DAY -> new PlcLTIME_OF_DAY(raw); + case DATE_AND_TIME -> // PlcDATE_AND_TIME expects seconds since 1970-01-01 - return new PlcDATE_AND_TIME(raw + IEC_DATE_EPOCH_OFFSET_DAYS * 86400L); - case DATE_AND_LTIME: + new PlcDATE_AND_TIME(raw + IEC_DATE_EPOCH_OFFSET_DAYS * 86400L); + case DATE_AND_LTIME -> // PlcDATE_AND_LTIME expects nanoseconds since 1970-01-01 - return new PlcDATE_AND_LTIME(raw + IEC_DATE_EPOCH_OFFSET_DAYS * 86400L * 1_000_000_000L); - case LDATE_AND_TIME: + new PlcDATE_AND_LTIME(raw + IEC_DATE_EPOCH_OFFSET_DAYS * 86400L * 1_000_000_000L); + case LDATE_AND_TIME -> // PlcLDATE_AND_TIME expects milliseconds since 1970-01-01 - return new PlcLDATE_AND_TIME(raw + IEC_DATE_EPOCH_OFFSET_DAYS * 86400L * 1000L); - default: - return value; - } + new PlcLDATE_AND_TIME(raw + IEC_DATE_EPOCH_OFFSET_DAYS * 86400L * 1000L); + default -> value; + }; + } + + private static boolean isTemporalType(PlcValueType type) { + return switch (type) { + case TIME, LTIME, DATE, LDATE, TIME_OF_DAY, LTIME_OF_DAY, DATE_AND_TIME, DATE_AND_LTIME, LDATE_AND_TIME -> + true; + default -> false; + }; } /** diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/readwrite/UnknownExtensionObject.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/readwrite/UnknownExtensionObject.java new file mode 100644 index 0000000000..b358a7a60e --- /dev/null +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/readwrite/UnknownExtensionObject.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.plc4x.java.opcua.readwrite; + +import java.util.Arrays; +import java.util.Objects; +import org.apache.plc4x.java.spi.generation.SerializationException; +import org.apache.plc4x.java.spi.generation.WriteBuffer; +import org.apache.plc4x.java.spi.generation.WriteBufferBoxBased; + +/** + * Represents an ExtensionObject whose type is not known to the parser. + * Stores the raw body bytes so they can be interpreted later based on context + * (e.g., the tag's dataType hint). + */ +public class UnknownExtensionObject extends ExtensionObjectDefinition { + + private final byte[] bodyBytes; + + public UnknownExtensionObject(byte[] bodyBytes) { + super(); + this.bodyBytes = bodyBytes; + } + + @Override + public Integer getExtensionId() { + return 0; + } + + public byte[] getBodyBytes() { + return bodyBytes; + } + + @Override + protected void serializeExtensionObjectDefinitionChild(WriteBuffer writeBuffer) + throws SerializationException { + writeBuffer.pushContext("UnknownExtensionObject"); + writeBuffer.writeByteArray("bodyBytes", bodyBytes); + writeBuffer.popContext("UnknownExtensionObject"); + } + + @Override + public int getLengthInBytes() { + return (int) Math.ceil((float) getLengthInBits() / 8.0); + } + + @Override + public int getLengthInBits() { + int lengthInBits = super.getLengthInBits(); + lengthInBits += bodyBytes.length * 8; + return lengthInBits; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UnknownExtensionObject)) return false; + UnknownExtensionObject that = (UnknownExtensionObject) o; + return super.equals(that) && Arrays.equals(bodyBytes, that.bodyBytes); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), Arrays.hashCode(bodyBytes)); + } + + @Override + public String toString() { + WriteBufferBoxBased writeBufferBoxBased = new WriteBufferBoxBased(true, true); + try { + writeBufferBoxBased.writeSerializable(this); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + return "\n" + writeBufferBoxBased.getBox().toString() + "\n"; + } +} diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/readwrite/utils/StaticHelper.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/readwrite/utils/StaticHelper.java index 7ae036ec39..3f67ecffb4 100644 --- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/readwrite/utils/StaticHelper.java +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/readwrite/utils/StaticHelper.java @@ -19,8 +19,13 @@ package org.apache.plc4x.java.opcua.readwrite.utils; import java.nio.charset.StandardCharsets; -import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; import org.apache.plc4x.java.opcua.readwrite.ExpandedNodeId; +import org.apache.plc4x.java.opcua.readwrite.ExtensionObjectDefinition; +import org.apache.plc4x.java.opcua.readwrite.UnknownExtensionObject; +import org.apache.plc4x.java.spi.generation.ParseException; +import org.apache.plc4x.java.spi.generation.ReadBuffer; +import org.apache.plc4x.java.spi.generation.SerializationException; +import org.apache.plc4x.java.spi.generation.WriteBuffer; public class StaticHelper { @@ -40,7 +45,34 @@ public class StaticHelper { try { return Integer.parseInt(expandedNodeId.getNodeId().getIdentifier()); } catch (NumberFormatException e) { - throw new PlcRuntimeException("Invalid node id, expected number, found " + expandedNodeId.getNodeId().getClass().getName()); + // Non-numeric NodeIds (e.g., vendor-specific types like Siemens TE_DTL) + // are not known to the parser. Return 0 to signal an unknown type. + return 0; } } + + public static ExtensionObjectDefinition parseExtensionObjectBody( + ReadBuffer readBuffer, int extensionId, int bodyLength) throws ParseException { + // Maintain the same "body" context that a simple field would produce, + // so the XML roundtrip tests stay consistent. + readBuffer.pullContext("body"); + ExtensionObjectDefinition result; + if (extensionId < 1 && bodyLength > 0) { + // Unknown extension object type (e.g., vendor-specific like Siemens TE_DTL). + // Read the raw body bytes so the buffer position stays correct. + byte[] rawBytes = readBuffer.readByteArray("unknownBody", bodyLength); + result = new UnknownExtensionObject(rawBytes); + } else { + result = ExtensionObjectDefinition.staticParse(readBuffer, extensionId); + } + readBuffer.closeContext("body"); + return result; + } + + public static void serializeExtensionObjectBody( + WriteBuffer writeBuffer, ExtensionObjectDefinition body) throws SerializationException { + writeBuffer.pushContext("body"); + body.serialize(writeBuffer); + writeBuffer.popContext("body"); + } } diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/ManualS71500NewFWDriverTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/ManualOpcUaS71500NewFWDriverTest.java similarity index 69% rename from plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/ManualS71500NewFWDriverTest.java rename to plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/ManualOpcUaS71500NewFWDriverTest.java index 66295cb58b..ee085fac17 100644 --- a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/ManualS71500NewFWDriverTest.java +++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/ManualOpcUaS71500NewFWDriverTest.java @@ -28,36 +28,37 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; -public class ManualS71500NewFWDriverTest extends ManualTest { +public class ManualOpcUaS71500NewFWDriverTest extends ManualTest { - public ManualS71500NewFWDriverTest(String connectionString) { - super(connectionString, true, false, true, true, 100); + public ManualOpcUaS71500NewFWDriverTest(String connectionString) { + super(connectionString, true, true, true, true, 100); } public static void main(String[] args) throws Exception { - boolean testArrays = true; - ManualS71500NewFWDriverTest test = new ManualS71500NewFWDriverTest("opcua://192.168.23.28:4840"); - test.addTestCase(/*"g_b1",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b1\"", new PlcBOOL(true)); - test.addTestCase(/*"g_b8",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b8\"", new PlcBYTE(0xAB)); - test.addTestCase(/*"g_s8",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_s8\"", new PlcSINT(-12)); - test.addTestCase(/*"g_u8",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_u8\"", new PlcUSINT(250)); - test.addTestCase(/*"g_b16",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b16\"", new PlcWORD(0xBEEF)); - test.addTestCase(/*"g_s16",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_s16\"", new PlcINT(-1234)); - test.addTestCase(/*"g_u16",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_u16\"", new PlcUINT(54321)); - test.addTestCase(/*"g_b32",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b32\"", new PlcDWORD(0xDEADBEEFL)); - test.addTestCase(/*"g_s32",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_s32\"", new PlcDINT(-12345678)); - test.addTestCase(/*"g_u32",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_u32\"", new PlcUDINT(305419896)); - test.addTestCase(/*"g_b64",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b64\"", new PlcLWORD(0x0123_4567_89AB_CDEFL)); - test.addTestCase(/*"g_s64",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_s64\"", new PlcLINT(-9223372036854770000L)); - test.addTestCase(/*"g_u64",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_u64\"", new PlcULINT(new BigDecimal("18446744073709551000"))); - test.addTestCase(/*"g_r32",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_r32\"", new PlcREAL(3.14159)); - test.addTestCase(/*"g_r64",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_r64\"", new PlcLREAL(2.71828182845905)); - test.addTestCase(/*"g_tim",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_tim\";TIME", new PlcTIME(2500)); // Is returned as Int32 - test.addTestCase(/*"g_dat",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_dat\";DATE", new PlcDATE(LocalDate.of(2025, 11, 12))); // Is returned as UInt16 - test.addTestCase(/*"g_timoday",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_timoday\";TIME_OF_DAY", new PlcTIME_OF_DAY(LocalTime.of(14, 33, 21, 250000000))); // Is returned as UInt32 -// test.addTestCase(/*"g_dattim",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_dattim\";LTIME", new PlcDATE_AND_LTIME(LocalDateTime.of(2025, 11, 12, 14, 33, 21, 500_000_000))); // TODO: Getting a class cast error, because OpcuaMessageResponse cannot be cast to OpcuaAPU - test.addTestCase(/*"g_str",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_str\"", new PlcSTRING("Hello PLC4X")); - test.addTestCase(/*"g_wstr",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_wstr\"", new PlcWSTRING("Grüße von PLC4X")); + boolean testArrays = false; + ManualOpcUaS71500NewFWDriverTest test = new ManualOpcUaS71500NewFWDriverTest("opcua://192.168.24.66:4840"); + test.addTestCase(/*"g_b1",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b1\";BOOL", new PlcBOOL(false)); + test.addTestCase(/*"g_b8",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b8\";BYTE", new PlcBYTE(0xBC)); + test.addTestCase(/*"g_s8",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_s8\";SINT", new PlcSINT(-14)); + test.addTestCase(/*"g_u8",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_u8\";USINT", new PlcUSINT(251)); +// test.addTestCase(/*"g_b16",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b16\";WORD", new PlcWORD(0xBEEF)); + test.addTestCase(/*"g_s16",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_s16\";INT", new PlcINT(-1235)); + test.addTestCase(/*"g_u16",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_u16\";UINT", new PlcUINT(54321)); +// test.addTestCase(/*"g_b32",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b32\";DWORD", new PlcDWORD(0xDEADBEEFL)); + test.addTestCase(/*"g_s32",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_s32\";DINT", new PlcDINT(-12345678)); + test.addTestCase(/*"g_u32",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_u32\";UDINT", new PlcUDINT(305419896)); +// test.addTestCase(/*"g_b64",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_b64\";LWORD", new PlcLWORD(0x0123_4567_89AB_CDEFL)); + test.addTestCase(/*"g_s64",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_s64\";LINT", new PlcLINT(-9223372036854770000L)); +// TODO: This seems to write different values to the plc. +// test.addTestCase(/*"g_u64",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_u64\";ULINT", new PlcULINT(new BigDecimal("18446744073709551000"))); + test.addTestCase(/*"g_r32",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_r32\";REAL", new PlcREAL(3.14159)); + test.addTestCase(/*"g_r64",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_r64\";LREAL", new PlcLREAL(2.71828182845905)); +// test.addTestCase(/*"g_tim",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_tim\";TIME", new PlcTIME(2500)); // Is returned as Int32 +// test.addTestCase(/*"g_dat",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_dat\";DATE", new PlcDATE(LocalDate.of(2025, 11, 12))); // Is returned as UInt16 +// test.addTestCase(/*"g_timoday",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_timoday\";TIME_OF_DAY", new PlcTIME_OF_DAY(LocalTime.of(14, 33, 21, 250000000))); // Is returned as UInt32 +// test.addTestCase(/*"g_dattim",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_dattim\";DATE_AND_TIME", new PlcDATE_AND_LTIME(LocalDateTime.of(2025, 11, 12, 14, 33, 21, 500_000_000))); + test.addTestCase(/*"g_str",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_str\";STRING", new PlcSTRING("Hello PLC4X")); +// test.addTestCase(/*"g_wstr",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_wstr\";WSTRING", new PlcWSTRING("Grüße von PLC4X")); if(testArrays) { test.addTestCase(/*"g_arrBool",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_arrBool\"", new PlcList(List.of( new PlcBOOL(true), new PlcBOOL(false), new PlcBOOL(true), new PlcBOOL(true), diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaParserSerializerTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaParserSerializerTest.java index 8fefde7336..4953b35091 100644 --- a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaParserSerializerTest.java +++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaParserSerializerTest.java @@ -23,7 +23,7 @@ import org.apache.plc4x.test.parserserializer.ParserSerializerTestsuiteRunner; public class OpcuaParserSerializerTest extends ParserSerializerTestsuiteRunner { public OpcuaParserSerializerTest() { - super("/protocols/opcua/ParserSerializerTestsuite.xml"); + super("/protocols/opcua/ParserSerializerTestsuite.xml", true); } } diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandleTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandleTest.java index 1339394a72..2e72af3e77 100644 --- a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandleTest.java +++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandleTest.java @@ -19,6 +19,7 @@ package org.apache.plc4x.java.opcua.protocol; import java.io.ByteArrayOutputStream; +import java.math.BigInteger; import java.util.concurrent.CountDownLatch; import java.util.stream.Stream; import org.apache.plc4x.java.DefaultPlcDriverManager; @@ -259,11 +260,11 @@ public class OpcuaSubscriptionHandleTest { Arguments.of(INT32_IDENTIFIER_READ_WRITE, Integer.class), Arguments.of(INT64_IDENTIFIER_READ_WRITE, Long.class), Arguments.of(INTEGER_IDENTIFIER_READ_WRITE, Integer.class), - Arguments.of(SBYTE_IDENTIFIER_READ_WRITE, byte[].class), + Arguments.of(SBYTE_IDENTIFIER_READ_WRITE, Byte.class), Arguments.of(STRING_IDENTIFIER_READ_WRITE, String.class), Arguments.of(UINT16_IDENTIFIER_READ_WRITE, Integer.class), Arguments.of(UINT32_IDENTIFIER_READ_WRITE, Long.class), - Arguments.of(UINT64_IDENTIFIER_READ_WRITE, Long.class), + Arguments.of(UINT64_IDENTIFIER_READ_WRITE, BigInteger.class), Arguments.of(UINTEGER_IDENTIFIER_READ_WRITE, Long.class) ); } diff --git a/plc4j/drivers/opcua/src/test/resources/logback-test.xml b/plc4j/drivers/opcua/src/test/resources/logback-test.xml index 719321b8e4..3e3748c42b 100644 --- a/plc4j/drivers/opcua/src/test/resources/logback-test.xml +++ b/plc4j/drivers/opcua/src/test/resources/logback-test.xml @@ -27,7 +27,7 @@ </encoder> </appender> - <root level="warn"> + <root level="info"> <appender-ref ref="STDOUT" /> </root> diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/Plc4xNettyWrapper.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/Plc4xNettyWrapper.java index 4c20b1390d..33f188dbec 100644 --- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/Plc4xNettyWrapper.java +++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/Plc4xNettyWrapper.java @@ -198,7 +198,9 @@ public class Plc4xNettyWrapper<T> extends MessageToMessageCodec<T, Object> { logger.trace("Failure while processing payload {} with handler {}", message, registration, e); BiConsumer biConsumer = registration.getErrorConsumer(); if(biConsumer != null) { - biConsumer.accept(message, e); + // Use the original payload rather than the transformed message, + // as the error consumer expects the original message type. + biConsumer.accept(payload, e); } registration.confirmError(); } diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/LegacyPlcValueHandler.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/LegacyPlcValueHandler.java index 0343bc4434..8666442a7c 100644 --- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/LegacyPlcValueHandler.java +++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/LegacyPlcValueHandler.java @@ -123,6 +123,10 @@ public class LegacyPlcValueHandler implements PlcValueHandler { public static PlcValue of(PlcTag tag, Object[] values) { if (values.length == 1) { + if (values[0] instanceof PlcValue) { + return (PlcValue) values[0]; + } + Object value = values[0]; if(tag.getPlcValueType() == null) { // TODO: This is a hacky shortcut .. diff --git a/protocols/opcua/src/main/generated/protocols/opcua/opc-manual.mspec b/protocols/opcua/src/main/generated/protocols/opcua/opc-manual.mspec index b2cb1610b7..bf6a048950 100644 --- a/protocols/opcua/src/main/generated/protocols/opcua/opc-manual.mspec +++ b/protocols/opcua/src/main/generated/protocols/opcua/opc-manual.mspec @@ -176,7 +176,7 @@ [typeSwitch encodingMask.xmlBody, encodingMask.binaryBody ['false', 'true' BinaryExtensionObjectWithMask [implicit int 32 bodyLength 'body == null ? 0 : body.lengthInBytes'] - [simple ExtensionObjectDefinition('extensionId') body] + [manual ExtensionObjectDefinition body 'STATIC_CALL("parseExtensionObjectBody", readBuffer, extensionId, bodyLength)' 'STATIC_CALL("serializeExtensionObjectBody", writeBuffer, body)' 'body == null ? 0 : body.lengthInBits'] ] ['false', 'false' NullExtensionObjectWithMask [virtual ExtensionObjectDefinition('0') body 'null'] diff --git a/protocols/opcua/src/main/xslt/opc-manual.xsl b/protocols/opcua/src/main/xslt/opc-manual.xsl index f5ea6879df..9bd6300a6c 100644 --- a/protocols/opcua/src/main/xslt/opc-manual.xsl +++ b/protocols/opcua/src/main/xslt/opc-manual.xsl @@ -225,7 +225,7 @@ [typeSwitch encodingMask.xmlBody, encodingMask.binaryBody ['false', 'true' BinaryExtensionObjectWithMask [implicit int 32 bodyLength 'body == null ? 0 : body.lengthInBytes'] - [simple ExtensionObjectDefinition('extensionId') body] + [manual ExtensionObjectDefinition body 'STATIC_CALL("parseExtensionObjectBody", readBuffer, extensionId, bodyLength)' 'STATIC_CALL("serializeExtensionObjectBody", writeBuffer, body)' 'body == null ? 0 : body.lengthInBits'] ] ['false', 'false' NullExtensionObjectWithMask [virtual ExtensionObjectDefinition('0') body 'null']
