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']