This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git


The following commit(s) were added to refs/heads/develop by this push:
     new 93d213ed02 fix: Fixed some of the UMAS write operations.
93d213ed02 is described below

commit 93d213ed0237311f733ddf693d0d0cad3dbf0f3d
Author: Christofer Dutz <[email protected]>
AuthorDate: Fri Apr 3 16:14:58 2026 +0200

    fix: Fixed some of the UMAS write operations.
---
 .../language/java/JavaLanguageTemplateHelper.java  |  4 +-
 .../apache/plc4x/java/umas/readwrite/DataItem.java | 70 +++++++++++++++++++---
 .../umas/readwrite/context/UmasDriverContext.java  | 49 +++++++++++++++
 .../umas/readwrite/protocol/UmasProtocolLogic.java | 65 +++++++++++++++++---
 .../main/resources/protocols/umas/v1/umas.mspec    | 47 ++++++++-------
 5 files changed, 194 insertions(+), 41 deletions(-)

diff --git 
a/code-generation/language/java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java
 
b/code-generation/language/java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java
index 0de32dd2a7..4827283e1d 100644
--- 
a/code-generation/language/java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java
+++ 
b/code-generation/language/java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java
@@ -619,7 +619,7 @@ public class JavaLanguageTemplateHelper extends 
BaseFreemarkerLanguageTemplateHe
                     return "_value.getDate().getDayOfMonth()";
                 case "dayOfWeek":
                     return "_value.getDate().getDayOfWeek().getValue()";
-                case "hour":
+                case "hour", "hours":
                     return "_value.getTime().getHour()";
                 case "minutes":
                     return "_value.getTime().getMinute()";
@@ -627,6 +627,8 @@ public class JavaLanguageTemplateHelper extends 
BaseFreemarkerLanguageTemplateHe
                     return "_value.getTime().getSecond()";
                 case "secondsSinceEpoch":
                     return 
"_value.getDateTime().toEpochSecond(ZoneOffset.UTC)";
+                case "centiseconds":
+                    return "(_value.getDuration().toMillis() / 10)";
                 case "milliseconds":
                     return "_value.getDuration().toMillis()";
                 case "millisecondsOfSecond":
diff --git 
a/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/DataItem.java
 
b/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/DataItem.java
index a1cf9888ea..c92e294702 100644
--- 
a/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/DataItem.java
+++ 
b/plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/DataItem.java
@@ -156,7 +156,8 @@ public class DataItem {
       return new PlcList(value);
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)
         && EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME
-      long value = readSimpleField("value", readUnsignedLong(readBuffer, 32));
+      long milliseconds = readSimpleField("milliseconds", 
readUnsignedLong(readBuffer, 32));
+      return PlcTIME.ofMilliseconds(milliseconds);
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)) { // List
       List<Long> _value =
           readCountArrayField("value", readUnsignedLong(readBuffer, 32), 
numberOfValues);
@@ -178,7 +179,21 @@ public class DataItem {
           readSimpleField("year", readUnsignedInt(readBuffer, 16), 
WithOption.WithEncoding("BCD"));
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)
         && EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME_OF_DAY
-      long value = readSimpleField("value", readUnsignedLong(readBuffer, 32));
+      short centiseconds =
+          readSimpleField(
+              "centiseconds", readUnsignedShort(readBuffer, 8), 
WithOption.WithEncoding("BCD"));
+
+      short seconds =
+          readSimpleField(
+              "seconds", readUnsignedShort(readBuffer, 8), 
WithOption.WithEncoding("BCD"));
+
+      short minutes =
+          readSimpleField(
+              "minutes", readUnsignedShort(readBuffer, 8), 
WithOption.WithEncoding("BCD"));
+
+      short hours =
+          readSimpleField(
+              "hours", readUnsignedShort(readBuffer, 8), 
WithOption.WithEncoding("BCD"));
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)) { // List
       List<Long> _value =
           readCountArrayField("value", readUnsignedLong(readBuffer, 32), 
numberOfValues);
@@ -309,7 +324,7 @@ public class DataItem {
       }
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)
         && EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME
-      // Simple field (value)
+      // Simple field (milliseconds)
       lengthInBits += 32;
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)) { // List
       // Array field
@@ -328,8 +343,17 @@ public class DataItem {
       lengthInBits += 16;
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)
         && EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME_OF_DAY
-      // Simple field (value)
-      lengthInBits += 32;
+      // Simple field (centiseconds)
+      lengthInBits += 8;
+
+      // Simple field (seconds)
+      lengthInBits += 8;
+
+      // Simple field (minutes)
+      lengthInBits += 8;
+
+      // Simple field (hours)
+      lengthInBits += 8;
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)) { // List
       // Array field
       if (_value != null) {
@@ -472,8 +496,11 @@ public class DataItem {
           writeFloat(writeBuffer, 32));
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)
         && EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME
-      // Simple Field (value)
-      writeSimpleField("value", (long) _value.getLong(), 
writeUnsignedLong(writeBuffer, 32));
+      // Simple Field (milliseconds)
+      writeSimpleField(
+          "milliseconds",
+          (long) _value.getDuration().toMillis(),
+          writeUnsignedLong(writeBuffer, 32));
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)) { // List
       // Array Field (value)
       writeSimpleTypeArrayField(
@@ -504,8 +531,33 @@ public class DataItem {
           WithOption.WithEncoding("BCD"));
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)
         && EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME_OF_DAY
-      // Simple Field (value)
-      writeSimpleField("value", (long) _value.getLong(), 
writeUnsignedLong(writeBuffer, 32));
+      // Simple Field (centiseconds)
+      writeSimpleField(
+          "centiseconds",
+          (short) (_value.getDuration().toMillis() / 10),
+          writeUnsignedShort(writeBuffer, 8),
+          WithOption.WithEncoding("BCD"));
+
+      // Simple Field (seconds)
+      writeSimpleField(
+          "seconds",
+          (short) _value.getTime().getSecond(),
+          writeUnsignedShort(writeBuffer, 8),
+          WithOption.WithEncoding("BCD"));
+
+      // Simple Field (minutes)
+      writeSimpleField(
+          "minutes",
+          (short) _value.getTime().getMinute(),
+          writeUnsignedShort(writeBuffer, 8),
+          WithOption.WithEncoding("BCD"));
+
+      // Simple Field (hours)
+      writeSimpleField(
+          "hours",
+          (short) _value.getTime().getHour(),
+          writeUnsignedShort(writeBuffer, 8),
+          WithOption.WithEncoding("BCD"));
     } else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)) { // List
       // Array Field (value)
       writeSimpleTypeArrayField(
diff --git 
a/plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/context/UmasDriverContext.java
 
b/plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/context/UmasDriverContext.java
index f13fa36da2..5c7ba3811a 100644
--- 
a/plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/context/UmasDriverContext.java
+++ 
b/plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/context/UmasDriverContext.java
@@ -75,6 +75,9 @@ public class UmasDriverContext implements DriverContext, 
HasConfiguration<UmasCo
     // Data type sizes from DD03: type ID -> allocated byte size
     private final Map<Integer, Integer> dataTypeSizes = new 
ConcurrentHashMap<>();
 
+    // Per-symbol allocated sizes computed from symbol table layout (gap 
between adjacent symbols)
+    private final Map<String, Integer> symbolSizes = new ConcurrentHashMap<>();
+
     @Override
     public void setConfiguration(UmasConfiguration configuration) {
         this.configuration = configuration;
@@ -240,4 +243,50 @@ public class UmasDriverContext implements DriverContext, 
HasConfiguration<UmasCo
         return Optional.ofNullable(dataTypeSizes.get(typeId));
     }
 
+    // --- Per-symbol size operations ---
+
+    /**
+     * Returns the computed allocated byte size for a symbol, or empty if not 
available.
+     *
+     * @param name the symbolic name (case-insensitive)
+     * @return the byte size if computed
+     */
+    public Optional<Integer> getSymbolSize(String name) {
+        return Optional.ofNullable(symbolSizes.get(name.toLowerCase()));
+    }
+
+    /**
+     * Computes the allocated byte size for each symbol from the symbol table 
layout.
+     * For each memory block, symbols are sorted by offset and the size is 
computed
+     * as the gap to the next symbol. The last symbol in each block gets no 
size entry.
+     * Must be called after all symbols have been loaded via {@link 
#addSymbol}.
+     */
+    public void computeSymbolSizes() {
+        symbolSizes.clear();
+
+        // Group symbols by block
+        java.util.Map<Integer, java.util.List<java.util.Map.Entry<String, 
UmasUnlocatedVariableReference>>> byBlock =
+            new java.util.HashMap<>();
+        for (var entry : symbolTable.entrySet()) {
+            byBlock.computeIfAbsent(entry.getValue().getBlock(), k -> new 
java.util.ArrayList<>()).add(entry);
+        }
+
+        // For each block, sort by offset and compute gaps
+        for (var blockEntry : byBlock.entrySet()) {
+            var symbols = blockEntry.getValue();
+            symbols.sort(java.util.Comparator.comparingLong(e -> 
e.getValue().getOffset()));
+
+            for (int i = 0; i < symbols.size() - 1; i++) {
+                var current = symbols.get(i);
+                var next = symbols.get(i + 1);
+                int size = (int) (next.getValue().getOffset() - 
current.getValue().getOffset());
+                if (size > 0) {
+                    symbolSizes.put(current.getKey(), size);
+                }
+            }
+        }
+
+        LOGGER.debug("Computed sizes for {} of {} symbols", 
symbolSizes.size(), symbolTable.size());
+    }
+
 }
diff --git 
a/plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/protocol/UmasProtocolLogic.java
 
b/plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/protocol/UmasProtocolLogic.java
index 4f9e6e3895..3ea37db61a 100644
--- 
a/plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/protocol/UmasProtocolLogic.java
+++ 
b/plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/protocol/UmasProtocolLogic.java
@@ -396,9 +396,12 @@ public class UmasProtocolLogic extends 
Plc4xProtocolBase<ModbusTcpADU> implement
 
         // STRING: requestSize=17 doesn't fit in the 4-bit dataSizeIndex field.
         // Read as a byte array instead: isArray=1, dataSizeIndex=1, 
arrayLength=bufferSize.
+        // Try the DD03 type size first, then the computed symbol size from 
the memory
+        // layout, and finally fall back to the default.
         if (UmasDataType.isDefined((short) dataTypeId)
                 && UmasDataType.enumForValue((short) dataTypeId) == 
UmasDataType.STRING) {
             int stringSize = umasDriverContext.getDataTypeSize(dataTypeId)
+                .or(() -> umasDriverContext.getSymbolSize(symbol.getValue()))
                 .orElse(DEFAULT_STRING_BUFFER_SIZE);
             return new VariableReadRequestReference(
                 (byte) 1, (byte) 1, symbol.getBlock(),
@@ -565,12 +568,12 @@ public class UmasProtocolLogic extends 
Plc4xProtocolBase<ModbusTcpADU> implement
     private VariableWriteRequestReference 
buildWriteReference(UmasUnlocatedVariableReference symbol, byte[] data) {
         int dataTypeId = symbol.getDataType();
 
-        // The symbol's 32-bit offset encodes two fields:
-        //   - lower 8 bits  → offset (uint 16 in 
VariableWriteRequestReference)
-        //   - upper bits     → baseOffset (uint 16 in 
VariableWriteRequestReference)
+        // Write references use a different offset encoding than read 
references:
+        // Read:  baseOffset = offset >> 8 (high 16 bits), offset = offset & 
0xFF (low 8 bits)
+        // Write: baseOffset = offset & 0xFFFF (low 16 bits), offset = (offset 
>> 16) & 0xFFFF (high 16 bits)
         long symbolOffset = symbol.getOffset();
-        int baseOffset = (int) (symbolOffset >> 8);
-        int offset = (int) (symbolOffset & 0xFF);
+        int baseOffset = (int) (symbolOffset & 0xFFFF);
+        int offset = (int) ((symbolOffset >> 16) & 0xFFFF);
 
         // STRING: requestSize=17 doesn't fit in 4-bit dataSizeIndex.
         // Write as byte array: isArray=1, dataSizeIndex=1, 
arrayLength=data.length.
@@ -644,15 +647,48 @@ public class UmasProtocolLogic extends 
Plc4xProtocolBase<ModbusTcpADU> implement
                 System.arraycopy(strBytes, 0, result, 0, strBytes.length);
                 yield result;
             }
-            case TIME, DATE, TOD -> {
+            case TIME -> {
+                // TIME is stored as uint32 milliseconds (not BCD)
                 ByteBuffer buf = 
ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN);
                 buf.putInt((int) (value.getLong() & 0xFFFFFFFFL));
                 yield buf.array();
             }
+            case DATE -> {
+                // DATE is BCD-encoded: day(1) + month(1) + year(2 LE)
+                java.time.LocalDate date = value.getDate();
+                byte[] result = new byte[4];
+                result[0] = encodeBcd(date.getDayOfMonth());
+                result[1] = encodeBcd(date.getMonthValue());
+                int year = date.getYear();
+                result[2] = encodeBcd(year % 100);
+                result[3] = encodeBcd(year / 100);
+                yield result;
+            }
+            case TOD -> {
+                // TOD is BCD-encoded: centiseconds(1) + seconds(1) + 
minutes(1) + hours(1)
+                java.time.LocalTime time = value.getTime();
+                byte[] result = new byte[4];
+                result[0] = encodeBcd((int) ((time.toNanoOfDay() / 10_000_000) 
% 100));
+                result[1] = encodeBcd(time.getSecond());
+                result[2] = encodeBcd(time.getMinute());
+                result[3] = encodeBcd(time.getHour());
+                yield result;
+            }
             case DATE_AND_TIME -> {
-                ByteBuffer buf = 
ByteBuffer.allocate(8).order(java.nio.ByteOrder.LITTLE_ENDIAN);
-                buf.putLong(value.getLong());
-                yield buf.array();
+                // DATE_AND_TIME: reserved(1) + seconds(1 BCD) + minutes(1 BCD)
+                // + hour(1 BCD) + day(1 BCD) + month(1 BCD) + year(2 BCD LE)
+                java.time.LocalDateTime dt = value.getDateTime();
+                byte[] result = new byte[8];
+                result[0] = 0x00;
+                result[1] = encodeBcd(dt.getSecond());
+                result[2] = encodeBcd(dt.getMinute());
+                result[3] = encodeBcd(dt.getHour());
+                result[4] = encodeBcd(dt.getDayOfMonth());
+                result[5] = encodeBcd(dt.getMonthValue());
+                int dtYear = dt.getYear();
+                result[6] = encodeBcd(dtYear % 100);
+                result[7] = encodeBcd(dtYear / 100);
+                yield result;
             }
             case WORD -> {
                 ByteBuffer buf = 
ByteBuffer.allocate(2).order(java.nio.ByteOrder.LITTLE_ENDIAN);
@@ -758,6 +794,9 @@ public class UmasProtocolLogic extends 
Plc4xProtocolBase<ModbusTcpADU> implement
             umasDriverContext.addSymbol(symbol.getValue(), symbol);
         }
 
+        // Compute per-symbol sizes from the memory layout (gap between 
adjacent symbols)
+        umasDriverContext.computeSymbolSizes();
+
         LOGGER.info("Data dictionary loaded: {} symbols", 
umasDriverContext.getSymbolCount());
     }
 
@@ -1037,6 +1076,14 @@ public class UmasProtocolLogic extends 
Plc4xProtocolBase<ModbusTcpADU> implement
         return decodeBcdByte(hi) * 100 + decodeBcdByte(lo);
     }
 
+    /**
+     * Encodes a decimal value (0-99) into a BCD byte.
+     * For example, 25 becomes 0x25 (high nibble = 2, low nibble = 5).
+     */
+    private static byte encodeBcd(int value) {
+        return (byte) (((value / 10) << 4) | (value % 10));
+    }
+
     private UmasPDUItem extractUmasResponse(ModbusTcpADU response, String 
stepName) throws PlcConnectionException {
         ModbusPDU pdu = response.getPdu();
         if (pdu instanceof ModbusPDUError errorPdu) {
diff --git a/protocols/umas/src/main/resources/protocols/umas/v1/umas.mspec 
b/protocols/umas/src/main/resources/protocols/umas/v1/umas.mspec
index 27fea9f422..ca29110be4 100644
--- a/protocols/umas/src/main/resources/protocols/umas/v1/umas.mspec
+++ b/protocols/umas/src/main/resources/protocols/umas/v1/umas.mspec
@@ -361,10 +361,10 @@
         // After registration, the variable's current value is returned
         // in subsequent read (0x07) responses.
         ['0x05'  MonitorPlcRegisterVariable
-            [simple     uint 8                   variableIndex  ]
-            [simple     uint 16                  block          ]
-            [simple     uint 16                  offset         ]
-            [simple     MonitorPlcRegisterAction action         ]
+            [simple     uint 8                   variableIndex]
+            [simple     uint 16                  block]
+            [simple     uint 16                  offset]
+            [simple     MonitorPlcRegisterAction action]
         ]
         // Read current values for all registered variables.
         // Returns concatenated raw values in the response — the driver
@@ -374,9 +374,9 @@
         // Register a variable AND include its value in the response.
         // Combines registration with an immediate read for that variable.
         ['0x09'  MonitorPlcRegisterAndRead
-            [simple     uint 8                   variableIndex  ]
-            [simple     uint 16                  block          ]
-            [simple     uint 16                  offset         ]
+            [simple     uint 8                  variableIndex]
+            [simple     uint 16                 block]
+            [simple     uint 16                 offset]
         ]
         // Clear/reset monitoring state. Observed in Modicon M340 captures
         // with no payload (single byte operation, like 0x07).
@@ -389,29 +389,29 @@
 [type UmasMemoryBlock(uint 16 blockNumber, uint 16 offset)
     [typeSwitch blockNumber, offset
         ['0x30', '0x00' UmasMemoryBlockBasicInfo
-            [simple     uint 16                  range          ]
-            [simple     uint 16                  notSure        ]
-            [simple     uint 8                   index          ]
-            [simple     uint 32                  hardwareId     ]
+            [simple     uint 16          range]
+            [simple uint 16 notSure]
+            [simple uint 8  index]
+            [simple uint 32 hardwareId]
         ]
     ]
 ]
 
 // Parsed response for unlocated variable names (used by driver layer)
 [type UmasPDUReadUnlocatedVariableNamesResponse
-    [simple     uint 8                           range                         
     ]
-    [simple     uint 16                          nextAddress                   
     ]
-    [simple     uint 16                          unknown1                      
     ]
-    [simple     uint 16                          noOfRecords                   
     ]
-    [array      UmasUnlocatedVariableReference   records        count 
'noOfRecords' ]
+    [simple     uint 8          range]
+    [simple     uint 16         nextAddress]
+    [simple     uint 16         unknown1]
+    [simple     uint 16         noOfRecords]
+    [array      UmasUnlocatedVariableReference         records count 
'noOfRecords']
 ]
 
 // Parsed response for UDT definitions
 [type UmasPDUReadUmasUDTDefinitionResponse
-    [simple     uint 8              range                           ]
-    [simple     uint 32             unknown1                        ]
-    [simple     uint 16             noOfRecords                     ]
-    [array      UmasUDTDefinition   records     count 'noOfRecords' ]
+    [simple     uint 8          range]
+    [simple     uint 32         unknown1]
+    [simple     uint 16         noOfRecords]
+    [array      UmasUDTDefinition         records count 'noOfRecords']
 ]
 
 // Parsed response for datatype names
@@ -552,7 +552,7 @@
             [array float 32 value count 'numberOfValues']
         ]
         ['TIME','1' TIME
-            [simple uint 32 value]
+            [simple uint 32 milliseconds]
         ]
         ['TIME' List
             [array uint 32 value count 'numberOfValues']
@@ -563,7 +563,10 @@
             [simple uint 16 year encoding='"BCD"']
         ]
         ['TOD','1' TIME_OF_DAY
-            [simple uint 32 value]
+            [simple uint 8 centiseconds encoding='"BCD"']
+            [simple uint 8 seconds encoding='"BCD"']
+            [simple uint 8 minutes encoding='"BCD"']
+            [simple uint 8 hours encoding='"BCD"']
         ]
         ['TOD' List
             [array uint 32 value count 'numberOfValues']

Reply via email to