From a7a95594944a4bb12fed403ad6448e8d21e15321 Mon Sep 17 00:00:00 2001 From: Mariusz Pacek Date: Sat, 27 Dec 2025 11:13:21 +0100 Subject: [PATCH] test(modbus): add parser tests for 4-byte and legacy-wrap decoding --- .../automation/modbus/ModbusParserTests.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/test/java/home/automation/modbus/ModbusParserTests.java diff --git a/src/test/java/home/automation/modbus/ModbusParserTests.java b/src/test/java/home/automation/modbus/ModbusParserTests.java new file mode 100644 index 0000000..870d0c7 --- /dev/null +++ b/src/test/java/home/automation/modbus/ModbusParserTests.java @@ -0,0 +1,89 @@ +package home.automation.modbus; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +class ModbusParserTests { + + private ModbusRtuTcpRepository createRepo() { + return new ModbusRtuTcpRepository(new Connection() { + @Override + public com.serotonin.modbus4j.ModbusMaster getConnection() { + throw new UnsupportedOperationException("not used in parser tests"); + } + }, java.util.Collections.emptyList()); + } + + private String invokeDecode(ModbusRtuTcpRepository repo, short[] data, String dataType, Double scale, Boolean legacy) throws Exception { + Method m = ModbusRtuTcpRepository.class.getDeclaredMethod("decodeValueFromShorts", short[].class, String.class, Double.class, Boolean.class); + m.setAccessible(true); + return (String) m.invoke(repo, (Object) data, dataType, scale, legacy); + } + + @Test + void decodeFourByteFloat() throws Exception { + ModbusRtuTcpRepository repo = createRepo(); + float value = 12.34f; + int bits = Float.floatToIntBits(value); + short high = (short) ((bits >>> 16) & 0xFFFF); + short low = (short) (bits & 0xFFFF); + String decoded = invokeDecode(repo, new short[]{high, low}, "FOUR_BYTE_FLOAT", 1.0, null); + assertEquals("12.34", decoded); + } + + @Test + void decodeFourByteFloatSwapped() throws Exception { + ModbusRtuTcpRepository repo = createRepo(); + float value = -5.5f; + int bits = Float.floatToIntBits(value); + short high = (short) ((bits >>> 16) & 0xFFFF); + short low = (short) (bits & 0xFFFF); + // swapped: pass low first, then high and use SWAPPED dataType + String decoded = invokeDecode(repo, new short[]{low, high}, "FOUR_BYTE_FLOAT_SWAPPED", 1.0, null); + // rounding to 2 decimals expected by implementation + assertEquals("-5.5", decoded); + } + + @Test + void decodeFourByteUnsignedInt() throws Exception { + ModbusRtuTcpRepository repo = createRepo(); + long unsignedVal = 3000000000L; // > 2^31 + int bits = (int) unsignedVal; // low 32 bits + short high = (short) ((bits >>> 16) & 0xFFFF); + short low = (short) (bits & 0xFFFF); + String decoded = invokeDecode(repo, new short[]{high, low}, "FOUR_BYTE_INT_UNSIGNED", 1.0, null); + assertEquals((double) unsignedVal, Double.parseDouble(decoded), 0.0001); + } + + @Test + void decodeFourByteSignedInt() throws Exception { + ModbusRtuTcpRepository repo = createRepo(); + int signedVal = -12345678; + int bits = signedVal; + short high = (short) ((bits >>> 16) & 0xFFFF); + short low = (short) (bits & 0xFFFF); + String decoded = invokeDecode(repo, new short[]{high, low}, "FOUR_BYTE_INT_SIGNED", 1.0, null); + assertEquals((double) signedVal, Double.parseDouble(decoded), 0.0001); + } + + @Test + void decodeTwoByteSignedLegacyWrap() throws Exception { + ModbusRtuTcpRepository repo = createRepo(); + short s = (short) (-32760); // near legacy wrap sentinel + String decoded = invokeDecode(repo, new short[]{s}, "TWO_BYTE_INT_SIGNED", 1.0, true); + // calculation: -(s + 32768) * scale -> -( -32760 + 32768) = -8 + assertEquals("-8.0", decoded); + } + + @Test + void decodeTwoByteNormal() throws Exception { + ModbusRtuTcpRepository repo = createRepo(); + short s = (short) (123); + String decoded = invokeDecode(repo, new short[]{s}, "TWO_BYTE_INT_SIGNED", 0.1, false); + // 123 * 0.1 = 12.3 + assertEquals("12.3", decoded); + } +} -- 2.54.0