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)
                 )),

Reply via email to