Skip to content

Commit 93d213e

Browse files
committed
fix: Fixed some of the UMAS write operations.
1 parent c66ee77 commit 93d213e

File tree

5 files changed

+194
-41
lines changed

5 files changed

+194
-41
lines changed

code-generation/language/java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,14 +619,16 @@ public String getDataIoPropertyValue(PropertyField propertyField) {
619619
return "_value.getDate().getDayOfMonth()";
620620
case "dayOfWeek":
621621
return "_value.getDate().getDayOfWeek().getValue()";
622-
case "hour":
622+
case "hour", "hours":
623623
return "_value.getTime().getHour()";
624624
case "minutes":
625625
return "_value.getTime().getMinute()";
626626
case "seconds":
627627
return "_value.getTime().getSecond()";
628628
case "secondsSinceEpoch":
629629
return "_value.getDateTime().toEpochSecond(ZoneOffset.UTC)";
630+
case "centiseconds":
631+
return "(_value.getDuration().toMillis() / 10)";
630632
case "milliseconds":
631633
return "_value.getDuration().toMillis()";
632634
case "millisecondsOfSecond":

plc4j/drivers/umas/src/main/generated/org/apache/plc4x/java/umas/readwrite/DataItem.java

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ public static PlcValue staticParse(
156156
return new PlcList(value);
157157
} else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)
158158
&& EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME
159-
long value = readSimpleField("value", readUnsignedLong(readBuffer, 32));
159+
long milliseconds = readSimpleField("milliseconds", readUnsignedLong(readBuffer, 32));
160+
return PlcTIME.ofMilliseconds(milliseconds);
160161
} else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)) { // List
161162
List<Long> _value =
162163
readCountArrayField("value", readUnsignedLong(readBuffer, 32), numberOfValues);
@@ -178,7 +179,21 @@ public static PlcValue staticParse(
178179
readSimpleField("year", readUnsignedInt(readBuffer, 16), WithOption.WithEncoding("BCD"));
179180
} else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)
180181
&& EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME_OF_DAY
181-
long value = readSimpleField("value", readUnsignedLong(readBuffer, 32));
182+
short centiseconds =
183+
readSimpleField(
184+
"centiseconds", readUnsignedShort(readBuffer, 8), WithOption.WithEncoding("BCD"));
185+
186+
short seconds =
187+
readSimpleField(
188+
"seconds", readUnsignedShort(readBuffer, 8), WithOption.WithEncoding("BCD"));
189+
190+
short minutes =
191+
readSimpleField(
192+
"minutes", readUnsignedShort(readBuffer, 8), WithOption.WithEncoding("BCD"));
193+
194+
short hours =
195+
readSimpleField(
196+
"hours", readUnsignedShort(readBuffer, 8), WithOption.WithEncoding("BCD"));
182197
} else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)) { // List
183198
List<Long> _value =
184199
readCountArrayField("value", readUnsignedLong(readBuffer, 32), numberOfValues);
@@ -309,7 +324,7 @@ public static int getLengthInBits(
309324
}
310325
} else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)
311326
&& EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME
312-
// Simple field (value)
327+
// Simple field (milliseconds)
313328
lengthInBits += 32;
314329
} else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)) { // List
315330
// Array field
@@ -328,8 +343,17 @@ public static int getLengthInBits(
328343
lengthInBits += 16;
329344
} else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)
330345
&& EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME_OF_DAY
331-
// Simple field (value)
332-
lengthInBits += 32;
346+
// Simple field (centiseconds)
347+
lengthInBits += 8;
348+
349+
// Simple field (seconds)
350+
lengthInBits += 8;
351+
352+
// Simple field (minutes)
353+
lengthInBits += 8;
354+
355+
// Simple field (hours)
356+
lengthInBits += 8;
333357
} else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)) { // List
334358
// Array field
335359
if (_value != null) {
@@ -472,8 +496,11 @@ public static void staticSerialize(
472496
writeFloat(writeBuffer, 32));
473497
} else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)
474498
&& EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME
475-
// Simple Field (value)
476-
writeSimpleField("value", (long) _value.getLong(), writeUnsignedLong(writeBuffer, 32));
499+
// Simple Field (milliseconds)
500+
writeSimpleField(
501+
"milliseconds",
502+
(long) _value.getDuration().toMillis(),
503+
writeUnsignedLong(writeBuffer, 32));
477504
} else if (EvaluationHelper.equals(dataType, UmasDataType.TIME)) { // List
478505
// Array Field (value)
479506
writeSimpleTypeArrayField(
@@ -504,8 +531,33 @@ public static void staticSerialize(
504531
WithOption.WithEncoding("BCD"));
505532
} else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)
506533
&& EvaluationHelper.equals(numberOfValues, (int) 1)) { // TIME_OF_DAY
507-
// Simple Field (value)
508-
writeSimpleField("value", (long) _value.getLong(), writeUnsignedLong(writeBuffer, 32));
534+
// Simple Field (centiseconds)
535+
writeSimpleField(
536+
"centiseconds",
537+
(short) (_value.getDuration().toMillis() / 10),
538+
writeUnsignedShort(writeBuffer, 8),
539+
WithOption.WithEncoding("BCD"));
540+
541+
// Simple Field (seconds)
542+
writeSimpleField(
543+
"seconds",
544+
(short) _value.getTime().getSecond(),
545+
writeUnsignedShort(writeBuffer, 8),
546+
WithOption.WithEncoding("BCD"));
547+
548+
// Simple Field (minutes)
549+
writeSimpleField(
550+
"minutes",
551+
(short) _value.getTime().getMinute(),
552+
writeUnsignedShort(writeBuffer, 8),
553+
WithOption.WithEncoding("BCD"));
554+
555+
// Simple Field (hours)
556+
writeSimpleField(
557+
"hours",
558+
(short) _value.getTime().getHour(),
559+
writeUnsignedShort(writeBuffer, 8),
560+
WithOption.WithEncoding("BCD"));
509561
} else if (EvaluationHelper.equals(dataType, UmasDataType.TOD)) { // List
510562
// Array Field (value)
511563
writeSimpleTypeArrayField(

plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/context/UmasDriverContext.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public class UmasDriverContext implements DriverContext, HasConfiguration<UmasCo
7575
// Data type sizes from DD03: type ID -> allocated byte size
7676
private final Map<Integer, Integer> dataTypeSizes = new ConcurrentHashMap<>();
7777

78+
// Per-symbol allocated sizes computed from symbol table layout (gap between adjacent symbols)
79+
private final Map<String, Integer> symbolSizes = new ConcurrentHashMap<>();
80+
7881
@Override
7982
public void setConfiguration(UmasConfiguration configuration) {
8083
this.configuration = configuration;
@@ -240,4 +243,50 @@ public Optional<Integer> getDataTypeSize(int typeId) {
240243
return Optional.ofNullable(dataTypeSizes.get(typeId));
241244
}
242245

246+
// --- Per-symbol size operations ---
247+
248+
/**
249+
* Returns the computed allocated byte size for a symbol, or empty if not available.
250+
*
251+
* @param name the symbolic name (case-insensitive)
252+
* @return the byte size if computed
253+
*/
254+
public Optional<Integer> getSymbolSize(String name) {
255+
return Optional.ofNullable(symbolSizes.get(name.toLowerCase()));
256+
}
257+
258+
/**
259+
* Computes the allocated byte size for each symbol from the symbol table layout.
260+
* For each memory block, symbols are sorted by offset and the size is computed
261+
* as the gap to the next symbol. The last symbol in each block gets no size entry.
262+
* Must be called after all symbols have been loaded via {@link #addSymbol}.
263+
*/
264+
public void computeSymbolSizes() {
265+
symbolSizes.clear();
266+
267+
// Group symbols by block
268+
java.util.Map<Integer, java.util.List<java.util.Map.Entry<String, UmasUnlocatedVariableReference>>> byBlock =
269+
new java.util.HashMap<>();
270+
for (var entry : symbolTable.entrySet()) {
271+
byBlock.computeIfAbsent(entry.getValue().getBlock(), k -> new java.util.ArrayList<>()).add(entry);
272+
}
273+
274+
// For each block, sort by offset and compute gaps
275+
for (var blockEntry : byBlock.entrySet()) {
276+
var symbols = blockEntry.getValue();
277+
symbols.sort(java.util.Comparator.comparingLong(e -> e.getValue().getOffset()));
278+
279+
for (int i = 0; i < symbols.size() - 1; i++) {
280+
var current = symbols.get(i);
281+
var next = symbols.get(i + 1);
282+
int size = (int) (next.getValue().getOffset() - current.getValue().getOffset());
283+
if (size > 0) {
284+
symbolSizes.put(current.getKey(), size);
285+
}
286+
}
287+
}
288+
289+
LOGGER.debug("Computed sizes for {} of {} symbols", symbolSizes.size(), symbolTable.size());
290+
}
291+
243292
}

plc4j/drivers/umas/src/main/java/org/apache/plc4x/java/umas/readwrite/protocol/UmasProtocolLogic.java

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,12 @@ private VariableReadRequestReference buildReadReference(UmasUnlocatedVariableRef
396396

397397
// STRING: requestSize=17 doesn't fit in the 4-bit dataSizeIndex field.
398398
// Read as a byte array instead: isArray=1, dataSizeIndex=1, arrayLength=bufferSize.
399+
// Try the DD03 type size first, then the computed symbol size from the memory
400+
// layout, and finally fall back to the default.
399401
if (UmasDataType.isDefined((short) dataTypeId)
400402
&& UmasDataType.enumForValue((short) dataTypeId) == UmasDataType.STRING) {
401403
int stringSize = umasDriverContext.getDataTypeSize(dataTypeId)
404+
.or(() -> umasDriverContext.getSymbolSize(symbol.getValue()))
402405
.orElse(DEFAULT_STRING_BUFFER_SIZE);
403406
return new VariableReadRequestReference(
404407
(byte) 1, (byte) 1, symbol.getBlock(),
@@ -565,12 +568,12 @@ private PlcResponseCode writeSingleTag(String tagName, PlcTag tag, PlcValue valu
565568
private VariableWriteRequestReference buildWriteReference(UmasUnlocatedVariableReference symbol, byte[] data) {
566569
int dataTypeId = symbol.getDataType();
567570

568-
// The symbol's 32-bit offset encodes two fields:
569-
// - lower 8 bits offset (uint 16 in VariableWriteRequestReference)
570-
// - upper bits → baseOffset (uint 16 in VariableWriteRequestReference)
571+
// Write references use a different offset encoding than read references:
572+
// Read: baseOffset = offset >> 8 (high 16 bits), offset = offset & 0xFF (low 8 bits)
573+
// Write: baseOffset = offset & 0xFFFF (low 16 bits), offset = (offset >> 16) & 0xFFFF (high 16 bits)
571574
long symbolOffset = symbol.getOffset();
572-
int baseOffset = (int) (symbolOffset >> 8);
573-
int offset = (int) (symbolOffset & 0xFF);
575+
int baseOffset = (int) (symbolOffset & 0xFFFF);
576+
int offset = (int) ((symbolOffset >> 16) & 0xFFFF);
574577

575578
// STRING: requestSize=17 doesn't fit in 4-bit dataSizeIndex.
576579
// Write as byte array: isArray=1, dataSizeIndex=1, arrayLength=data.length.
@@ -644,15 +647,48 @@ private static byte[] serializeForType(UmasDataType umasType, PlcValue value) {
644647
System.arraycopy(strBytes, 0, result, 0, strBytes.length);
645648
yield result;
646649
}
647-
case TIME, DATE, TOD -> {
650+
case TIME -> {
651+
// TIME is stored as uint32 milliseconds (not BCD)
648652
ByteBuffer buf = ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN);
649653
buf.putInt((int) (value.getLong() & 0xFFFFFFFFL));
650654
yield buf.array();
651655
}
656+
case DATE -> {
657+
// DATE is BCD-encoded: day(1) + month(1) + year(2 LE)
658+
java.time.LocalDate date = value.getDate();
659+
byte[] result = new byte[4];
660+
result[0] = encodeBcd(date.getDayOfMonth());
661+
result[1] = encodeBcd(date.getMonthValue());
662+
int year = date.getYear();
663+
result[2] = encodeBcd(year % 100);
664+
result[3] = encodeBcd(year / 100);
665+
yield result;
666+
}
667+
case TOD -> {
668+
// TOD is BCD-encoded: centiseconds(1) + seconds(1) + minutes(1) + hours(1)
669+
java.time.LocalTime time = value.getTime();
670+
byte[] result = new byte[4];
671+
result[0] = encodeBcd((int) ((time.toNanoOfDay() / 10_000_000) % 100));
672+
result[1] = encodeBcd(time.getSecond());
673+
result[2] = encodeBcd(time.getMinute());
674+
result[3] = encodeBcd(time.getHour());
675+
yield result;
676+
}
652677
case DATE_AND_TIME -> {
653-
ByteBuffer buf = ByteBuffer.allocate(8).order(java.nio.ByteOrder.LITTLE_ENDIAN);
654-
buf.putLong(value.getLong());
655-
yield buf.array();
678+
// DATE_AND_TIME: reserved(1) + seconds(1 BCD) + minutes(1 BCD)
679+
// + hour(1 BCD) + day(1 BCD) + month(1 BCD) + year(2 BCD LE)
680+
java.time.LocalDateTime dt = value.getDateTime();
681+
byte[] result = new byte[8];
682+
result[0] = 0x00;
683+
result[1] = encodeBcd(dt.getSecond());
684+
result[2] = encodeBcd(dt.getMinute());
685+
result[3] = encodeBcd(dt.getHour());
686+
result[4] = encodeBcd(dt.getDayOfMonth());
687+
result[5] = encodeBcd(dt.getMonthValue());
688+
int dtYear = dt.getYear();
689+
result[6] = encodeBcd(dtYear % 100);
690+
result[7] = encodeBcd(dtYear / 100);
691+
yield result;
656692
}
657693
case WORD -> {
658694
ByteBuffer buf = ByteBuffer.allocate(2).order(java.nio.ByteOrder.LITTLE_ENDIAN);
@@ -758,6 +794,9 @@ private void loadDataDictionary() throws Exception {
758794
umasDriverContext.addSymbol(symbol.getValue(), symbol);
759795
}
760796

797+
// Compute per-symbol sizes from the memory layout (gap between adjacent symbols)
798+
umasDriverContext.computeSymbolSizes();
799+
761800
LOGGER.info("Data dictionary loaded: {} symbols", umasDriverContext.getSymbolCount());
762801
}
763802

@@ -1037,6 +1076,14 @@ private static int decodeBcd16(byte lo, byte hi) {
10371076
return decodeBcdByte(hi) * 100 + decodeBcdByte(lo);
10381077
}
10391078

1079+
/**
1080+
* Encodes a decimal value (0-99) into a BCD byte.
1081+
* For example, 25 becomes 0x25 (high nibble = 2, low nibble = 5).
1082+
*/
1083+
private static byte encodeBcd(int value) {
1084+
return (byte) (((value / 10) << 4) | (value % 10));
1085+
}
1086+
10401087
private UmasPDUItem extractUmasResponse(ModbusTcpADU response, String stepName) throws PlcConnectionException {
10411088
ModbusPDU pdu = response.getPdu();
10421089
if (pdu instanceof ModbusPDUError errorPdu) {

protocols/umas/src/main/resources/protocols/umas/v1/umas.mspec

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,10 @@
361361
// After registration, the variable's current value is returned
362362
// in subsequent read (0x07) responses.
363363
['0x05' MonitorPlcRegisterVariable
364-
[simple uint 8 variableIndex ]
365-
[simple uint 16 block ]
366-
[simple uint 16 offset ]
367-
[simple MonitorPlcRegisterAction action ]
364+
[simple uint 8 variableIndex]
365+
[simple uint 16 block]
366+
[simple uint 16 offset]
367+
[simple MonitorPlcRegisterAction action]
368368
]
369369
// Read current values for all registered variables.
370370
// Returns concatenated raw values in the response — the driver
@@ -374,9 +374,9 @@
374374
// Register a variable AND include its value in the response.
375375
// Combines registration with an immediate read for that variable.
376376
['0x09' MonitorPlcRegisterAndRead
377-
[simple uint 8 variableIndex ]
378-
[simple uint 16 block ]
379-
[simple uint 16 offset ]
377+
[simple uint 8 variableIndex]
378+
[simple uint 16 block]
379+
[simple uint 16 offset]
380380
]
381381
// Clear/reset monitoring state. Observed in Modicon M340 captures
382382
// with no payload (single byte operation, like 0x07).
@@ -389,29 +389,29 @@
389389
[type UmasMemoryBlock(uint 16 blockNumber, uint 16 offset)
390390
[typeSwitch blockNumber, offset
391391
['0x30', '0x00' UmasMemoryBlockBasicInfo
392-
[simple uint 16 range ]
393-
[simple uint 16 notSure ]
394-
[simple uint 8 index ]
395-
[simple uint 32 hardwareId ]
392+
[simple uint 16 range]
393+
[simple uint 16 notSure]
394+
[simple uint 8 index]
395+
[simple uint 32 hardwareId]
396396
]
397397
]
398398
]
399399
400400
// Parsed response for unlocated variable names (used by driver layer)
401401
[type UmasPDUReadUnlocatedVariableNamesResponse
402-
[simple uint 8 range ]
403-
[simple uint 16 nextAddress ]
404-
[simple uint 16 unknown1 ]
405-
[simple uint 16 noOfRecords ]
406-
[array UmasUnlocatedVariableReference records count 'noOfRecords' ]
402+
[simple uint 8 range]
403+
[simple uint 16 nextAddress]
404+
[simple uint 16 unknown1]
405+
[simple uint 16 noOfRecords]
406+
[array UmasUnlocatedVariableReference records count 'noOfRecords']
407407
]
408408
409409
// Parsed response for UDT definitions
410410
[type UmasPDUReadUmasUDTDefinitionResponse
411-
[simple uint 8 range ]
412-
[simple uint 32 unknown1 ]
413-
[simple uint 16 noOfRecords ]
414-
[array UmasUDTDefinition records count 'noOfRecords' ]
411+
[simple uint 8 range]
412+
[simple uint 32 unknown1]
413+
[simple uint 16 noOfRecords]
414+
[array UmasUDTDefinition records count 'noOfRecords']
415415
]
416416
417417
// Parsed response for datatype names
@@ -552,7 +552,7 @@
552552
[array float 32 value count 'numberOfValues']
553553
]
554554
['TIME','1' TIME
555-
[simple uint 32 value]
555+
[simple uint 32 milliseconds]
556556
]
557557
['TIME' List
558558
[array uint 32 value count 'numberOfValues']
@@ -563,7 +563,10 @@
563563
[simple uint 16 year encoding='"BCD"']
564564
]
565565
['TOD','1' TIME_OF_DAY
566-
[simple uint 32 value]
566+
[simple uint 8 centiseconds encoding='"BCD"']
567+
[simple uint 8 seconds encoding='"BCD"']
568+
[simple uint 8 minutes encoding='"BCD"']
569+
[simple uint 8 hours encoding='"BCD"']
567570
]
568571
['TOD' List
569572
[array uint 32 value count 'numberOfValues']

0 commit comments

Comments
 (0)