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
The following commit(s) were added to refs/heads/fix/opc-ua-fine-tuning by this
push:
new fb73288afa chore: Added the option to provide an optional type,
telling the driver how to decode the temporal values.
fb73288afa is described below
commit fb73288afa4ceaa1992ed7f14aa9004878549097
Author: Christofer Dutz <[email protected]>
AuthorDate: Fri Feb 13 15:18:12 2026 +0100
chore: Added the option to provide an optional type, telling the driver how
to decode the temporal values.
---
.../java/opcua/protocol/OpcuaProtocolLogic.java | 59 ++++++++++++++++++++++
.../java/opcua/ManualS71500NewFWDriverTest.java | 16 +++---
2 files changed, 67 insertions(+), 8 deletions(-)
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 5a459a4216..9d50a2f922 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
@@ -64,8 +64,10 @@ import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.time.Duration;
+import java.time.LocalDate;
import java.time.Instant;
import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -87,6 +89,8 @@ public class OpcuaProtocolLogic extends
Plc4xProtocolBase<OpcuaAPU> implements H
new ExtensionObjectEncodingMask(false, false, false));
private static final long EPOCH_OFFSET = 116444736000000000L;
//Offset between OPC UA epoch time and linux epoch time.
+ // IEC 61131-3 date types use 1990-01-01 as epoch, PlcDATE etc. use Unix
epoch (1970-01-01).
+ private static final long IEC_DATE_EPOCH_OFFSET_DAYS = LocalDate.of(1990,
1, 1).toEpochDay();
private final Map<Long, OpcuaSubscriptionHandle> subscriptions = new
ConcurrentHashMap<>();
private final RequestTransactionManager tm = new
RequestTransactionManager();
@@ -472,9 +476,64 @@ public class OpcuaProtocolLogic extends
Plc4xProtocolBase<OpcuaAPU> implements H
}
value = structurePlcValues(values, variant);
}
+
+ // If the tag declares a specific type (via suffix like :TIME, :DATE,
etc.),
+ // re-interpret the raw numeric value as the correct IEC 61131-3 type.
+ if (value != null && tag != null) {
+ PlcValueType targetType = tag.getPlcValueType();
+ if (targetType != PlcValueType.NULL) {
+ value = applyTypeOverride(value, targetType);
+ }
+ }
+
return value;
}
+ /**
+ * 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;
+ List<PlcValue> converted = new ArrayList<>(list.getLength());
+ for (PlcValue item : list.getList()) {
+ converted.add(applyTypeOverride(item, targetType));
+ }
+ return new PlcList(converted);
+ }
+ long raw = value.getLong();
+ switch (targetType) {
+ case TIME:
+ return new PlcTIME(raw);
+ case LTIME:
+ return 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:
+ // PlcLDATE expects seconds since 1970-01-01
+ return 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:
+ // 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:
+ // 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:
+ // 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;
+ }
+ }
+
/**
* Structures a flat list of PlcValues according to the variant's
dimensionality:
* - Single value: returns the scalar PlcValue directly
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/ManualS71500NewFWDriverTest.java
index d49a884ecd..3060f664dc 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/ManualS71500NewFWDriverTest.java
@@ -52,10 +52,10 @@ public class ManualS71500NewFWDriverTest extends ManualTest
{
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\"", new PlcTIME(2500)); // TODO: Is returned as Int32
-// test.addTestCase(/*"g_dat",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC
Data\".\"g_dat\"", new PlcDATE(LocalDate.of(2025, 11, 12))); // TODO:
Is returned as UInt16
-// test.addTestCase(/*"g_timoday",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC
Data\".\"g_timoday\"", new PlcTIME_OF_DAY(LocalTime.of(14, 33, 21,
250000000))); // TODO: Is returned as UInt32
-// test.addTestCase(/*"g_dattim",*/ "ns=3;s=\"OPC_UA_DB\".\"OPC
Data\".\"g_dattim\"", 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_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"));
if(testArrays) {
@@ -82,16 +82,16 @@ public class ManualS71500NewFWDriverTest extends ManualTest
{
test.addTestCase(/*"g_arrLReal",*/
"ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_arrLReal\"", new PlcList(List.of(
new PlcLREAL(1.5), new PlcLREAL(-2.0), new PlcLREAL(0.125))
));
-// test.addTestCase(/*"g_arrTime",*/
"ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_arrTime\"", new PlcList(List.of(
-// new PlcTIME(Duration.ofMillis(10)), new
PlcTIME(Duration.ofSeconds(1)), new PlcTIME(Duration.ofSeconds(10)))
-// ));
+ test.addTestCase(/*"g_arrTime",*/
"ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_arrTime\";TIME", new PlcList(List.of(
+ new PlcTIME(Duration.ofMillis(10)), new
PlcTIME(Duration.ofSeconds(1)), new PlcTIME(Duration.ofSeconds(10)))
+ ));
test.addTestCase(/*"g_arrString",*/
"ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_arrString\"", new PlcList(List.of(
new PlcSTRING("alpha"), new PlcSTRING("beta"), new
PlcSTRING("gamma"))
));
test.addTestCase(/*"g_arrWString",*/
"ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_arrWString\"", new PlcList(List.of(
new PlcWSTRING("Äpfel"), new PlcWSTRING("Öl"))
));
- test.addTestCase(/*"g_matI16_2x3",*/
"ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_matI16_2x3\"", new PlcList(List.of( //
TODO: Getting a class cast error, because OpcuaMessageResponse cannot be cast
to OpcuaAPU
+ test.addTestCase(/*"g_matI16_2x3",*/
"ns=3;s=\"OPC_UA_DB\".\"OPC Data\".\"g_matI16_2x3\"", new PlcList(List.of(
new PlcList(List.of(
new PlcINT(10), new PlcINT(11), new PlcINT(12)
)),