fix(driver-fins): correct 32-bit read length and FLOAT write encoding

Two runtime bugs in the FINS driver:

1. read() hardcoded a 1-word (2-byte) memory-read request regardless of
   dataType, so INT32/UINT32/FLOAT decodes (which need 4 bytes) always
   threw BufferUnderflowException — 32-bit points could never be read.
   Derive the word count from dataType (2 words for 32-bit types).

2. encodeWriteData grouped FLOAT with INT32/UINT32, doing
   Integer.parseInt + putInt. Writing "3.14" threw NumberFormatException
   and "3" was written as int bits (read back as ~4.2E-45 via getFloat).
   Give FLOAT its own branch using Float.parseFloat + putFloat.

Relaxed encodeWriteData/decodeValue/wordCount to package-private and
added round-trip tests (FLOAT, INT32) plus wordCount coverage.
Verified: dc3-driver-fins 7 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pnoker
2026-07-14 17:45:18 +08:00
co-authored by Claude Fable 5
parent 3a2de55f4b
commit b01b2802db
2 changed files with 51 additions and 5 deletions
@@ -164,7 +164,7 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
try {
byte areaCode = resolveMemoryAreaCode(memoryArea);
byte[] finsFrame = buildMemoryReadFrame(driverConfig, areaCode, address, 1);
byte[] finsFrame = buildMemoryReadFrame(driverConfig, areaCode, address, wordCount(dataType));
byte[] response = sendFinsCommand(socket, finsFrame);
String value = parseReadResponse(response, dataType);
@@ -402,7 +402,7 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
* @param dataType data type identifier
* @return decoded value string
*/
private String decodeValue(byte[] data, String dataType) {
String decodeValue(byte[] data, String dataType) {
return switch (dataType.toUpperCase()) {
case "INT16" -> String.valueOf(ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).getShort());
case "UINT16" ->
@@ -417,6 +417,20 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
};
}
/**
* Resolve the FINS memory read length in words (2 bytes each) for a data type.
* 32-bit types (INT32/UINT32/FLOAT) span 2 words; all others read a single word.
*
* @param dataType data type identifier
* @return word count to request
*/
int wordCount(String dataType) {
return switch (dataType.toUpperCase()) {
case "INT32", "UINT32", "FLOAT" -> 2;
default -> 1;
};
}
/**
* Encode a string value to bytes for FINS write operations.
*
@@ -424,7 +438,7 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
* @param dataType data type identifier
* @return encoded bytes
*/
private byte[] encodeWriteData(String value, String dataType) {
byte[] encodeWriteData(String value, String dataType) {
byte[] data;
switch (dataType.toUpperCase()) {
case "INT16":
@@ -435,13 +449,18 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
break;
}
case "INT32":
case "UINT32":
case "FLOAT": {
case "UINT32": {
data = new byte[4];
int i = Integer.parseInt(value);
ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).putInt(i);
break;
}
case "FLOAT": {
data = new byte[4];
float f = Float.parseFloat(value);
ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).putFloat(f);
break;
}
case "STRING": {
data = value.getBytes();
break;
@@ -91,4 +91,31 @@ class FinsDriverCustomServiceImplTest {
assertThatNoException().isThrownBy(() -> service.schedule());
}
@Test
void wordCountIsTwoFor32BitTypesAndOneOtherwise() {
assertThat(service.wordCount("INT32")).isEqualTo(2);
assertThat(service.wordCount("UINT32")).isEqualTo(2);
assertThat(service.wordCount("FLOAT")).isEqualTo(2);
assertThat(service.wordCount("float")).isEqualTo(2);
assertThat(service.wordCount("INT16")).isEqualTo(1);
assertThat(service.wordCount("UINT16")).isEqualTo(1);
assertThat(service.wordCount("STRING")).isEqualTo(1);
}
@Test
void floatEncodeDecodeRoundTrips() {
byte[] encoded = service.encodeWriteData("3.14", "FLOAT");
assertThat(encoded).hasSize(4);
assertThat(Float.parseFloat(service.decodeValue(encoded, "FLOAT"))).isEqualTo(3.14f);
}
@Test
void int32EncodeDecodeRoundTrips() {
byte[] encoded = service.encodeWriteData("70000", "INT32");
assertThat(encoded).hasSize(4);
assertThat(service.decodeValue(encoded, "INT32")).isEqualTo("70000");
}
}