From 7e0a9bbebfd48dfb3cfb88a4687bd284638f6f2b Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 22 Jul 2024 17:39:46 +0300 Subject: [PATCH] tbel: added some float methods --- .../thingsboard/script/api/tbel/TbUtils.java | 163 ++++++++++++------ .../script/api/tbel/TbUtilsTest.java | 75 +++++++- 2 files changed, 181 insertions(+), 57 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index 852f7a054f..034d6b4686 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -61,6 +61,7 @@ public class TbUtils { private static final int HEX_LEN_MIN = -1; private static final int HEX_LEN_INT_MAX = 8; private static final int HEX_LEN_LONG_MAX = 16; + private static final int BYTES_LEN_INT_MAX = 4; private static final int BYTES_LEN_LONG_MAX = 8; private static final LinkedHashMap mdnEncodingReplacements = new LinkedHashMap<>(); @@ -117,6 +118,8 @@ public class TbUtils { String.class))); parserConfig.addImport("parseFloat", new MethodStub(TbUtils.class.getMethod("parseFloat", String.class, int.class))); + parserConfig.addImport("parseHexIntLongToFloat", new MethodStub(TbUtils.class.getMethod("parseHexIntLongToFloat", + String.class, boolean.class))); parserConfig.addImport("parseDouble", new MethodStub(TbUtils.class.getMethod("parseDouble", String.class))); parserConfig.addImport("parseLittleEndianHexToInt", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToInt", @@ -159,14 +162,18 @@ public class TbUtils { String.class))); parserConfig.addImport("parseHexToFloat", new MethodStub(TbUtils.class.getMethod("parseHexToFloat", String.class, boolean.class))); - parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", - byte[].class, int.class, boolean.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", byte[].class, int.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + List.class, int.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + byte[].class, int.class, boolean.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", List.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", + byte[].class, int.class, int.class, boolean.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", - List.class, int.class))); + List.class, int.class, int.class, boolean.class))); parserConfig.addImport("parseLittleEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToDouble", String.class))); parserConfig.addImport("parseBigEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseBigEndianHexToDouble", @@ -201,17 +208,17 @@ public class TbUtils { Long.class))); parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", Long.class, boolean.class))); - parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", + parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", Long.class, boolean.class, boolean.class))); - parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", + parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex", Long.class, boolean.class, boolean.class, int.class))); - parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", + parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", Long.class))); - parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", + parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", Long.class, int.class))); - parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", + parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", Long.class, int.class, boolean.class))); - parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", + parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString", Long.class, int.class, boolean.class, boolean.class))); parserConfig.addImport("floatToHex", new MethodStub(TbUtils.class.getMethod("floatToHex", Float.class))); @@ -334,10 +341,14 @@ public class TbUtils { } public static Integer parseInt(String value, int radix) { - if (StringUtils.isNotBlank(value)) { - String valueP = prepareNumberString(value); + return parseInt(value, radix, true); + } + + private static Integer parseInt(String value, int radix, boolean bigEndian) { + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { int radixValue = isValidStringAndRadix(valueP, radix, value); - if (radixValue >= 25 && radixValue <= MAX_RADIX) { + if (radixValue >= 25 && radixValue <= MAX_RADIX) { return (Integer) compareIntLongValueMinMax(valueP, radixValue, Integer.MAX_VALUE, Integer.MIN_VALUE); } return switch (radixValue) { @@ -354,10 +365,14 @@ public class TbUtils { } public static Long parseLong(String value, int radix) { - if (StringUtils.isNotBlank(value)) { - String valueP = prepareNumberString(value); + return parseLong(value, radix, true); + } + + private static Long parseLong(String value, int radix, boolean bigEndian) { + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { int radixValue = isValidStringAndRadix(valueP, radix, value); - if (radixValue >= 25 && radixValue <= MAX_RADIX) { + if (radixValue >= 25 && radixValue <= MAX_RADIX) { return (Long) compareIntLongValueMinMax(valueP, radixValue, Long.MAX_VALUE, Long.MIN_VALUE); } return switch (radixValue) { @@ -385,6 +400,7 @@ public class TbUtils { return Integer.parseInt(binaryString, MIN_RADIX); } } + private static long parseBinaryStringAsSignedLong(String binaryString) { if (binaryString.length() != 64) { // Pad the binary string to 64 bits if it is not already @@ -420,34 +436,58 @@ public class TbUtils { } public static Float parseFloat(String value) { - return parseFloat(value, 0); + return parseFloat(value, ZERO_RADIX); } public static Float parseFloat(String value, int radix) { - if (StringUtils.isNotBlank(value)) { - String valueP = prepareNumberString(value); - int radixValue = isValidStringAndRadix(valueP, radix, value); - if (radixValue == DEC_RADIX) { - return Float.parseFloat(value); - } else { - int bits = Integer.parseUnsignedInt(valueP, HEX_RADIX); - return Float.intBitsToFloat(bits); + String valueP = prepareNumberString(value, true); + if (valueP != null) { + return parseFloatFromString(value, valueP, radix); + } + return null; + } + + private static Float parseFloatFromString(String value, String valueP, int radix) { + int radixValue = isValidStringAndRadix(valueP, radix, value); + if (radixValue == HEX_RADIX) { + int bits = (int) Long.parseLong(valueP, HEX_RADIX); + // Hex representation is a standard IEEE 754 float value (eg "0x41200000" for 10.0f). + return Float.intBitsToFloat(bits); + } else { + return Float.parseFloat(value); + } + } + + public static Float parseHexIntLongToFloat(String value, boolean bigEndian) { + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { + int radixValue = isValidStringAndRadix(valueP, HEX_RADIX, value); + if (radixValue == HEX_RADIX) { + int bits = (int) Long.parseLong(valueP, HEX_RADIX); + // If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f). + float floatValue = (float) bits; + return Float.valueOf(floatValue); } } return null; } + public static Double parseDouble(String value) { int radix = getRadix10_16(value); return parseDouble(value, radix); } public static Double parseDouble(String value, int radix) { - if (value != null) { - String valueP = prepareNumberString(value); + return parseDouble(value, radix, true); + } + + private static Double parseDouble(String value, int radix, boolean bigEndian) { + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { int radixValue = isValidStringAndRadix(valueP, radix, value); if (radixValue == DEC_RADIX) { - return Double.parseDouble(prepareNumberString(value)); + return Double.parseDouble(valueP); } else { long bits = Long.parseUnsignedLong(valueP, HEX_RADIX); return Double.longBitsToDouble(bits); @@ -469,9 +509,7 @@ public class TbUtils { } public static Integer parseHexToInt(String value, boolean bigEndian) { - String hexValue = prepareNumberString(value); - String hex = bigEndian ? hexValue : reverseHexStringByOrder(hexValue); - return parseInt(hex, HEX_RADIX); + return parseInt(value, HEX_RADIX, bigEndian); } public static long parseLittleEndianHexToLong(String hex) { @@ -487,9 +525,7 @@ public class TbUtils { } public static Long parseHexToLong(String value, boolean bigEndian) { - String hexValue = prepareNumberString(value); - String hex = bigEndian ? value : reverseHexStringByOrder(hexValue); - return parseLong(hex, HEX_RADIX); + return parseLong(value, HEX_RADIX, bigEndian); } public static float parseLittleEndianHexToFloat(String hex) { @@ -505,9 +541,11 @@ public class TbUtils { } public static Float parseHexToFloat(String value, boolean bigEndian) { - String hexValue = prepareNumberString(value); - String hex = bigEndian ? value : reverseHexStringByOrder(hexValue); - return parseFloat(hex, HEX_RADIX); + String valueP = prepareNumberString(value, bigEndian); + if (valueP != null) { + return parseFloatFromString(value, valueP, HEX_RADIX); + } + return null; } public static double parseLittleEndianHexToDouble(String hex) { @@ -523,13 +561,11 @@ public class TbUtils { } public static double parseHexToDouble(String value, boolean bigEndian) { - String hexValue = prepareNumberString(value); - String hex = bigEndian ? value : reverseHexStringByOrder(hexValue); - return parseDouble(hex, HEX_RADIX); + return parseDouble(value, HEX_RADIX, bigEndian); } public static ExecutionArrayList hexToBytes(ExecutionContext ctx, String value) { - String hex = prepareNumberString(value); + String hex = prepareNumberString(value, true); int len = hex.length(); if (len % 2 > 0) { throw new IllegalArgumentException("Hex string must be even-length."); @@ -575,6 +611,7 @@ public class TbUtils { public static String longToHex(Long l) { return prepareNumberHexString(l, true, false, HEX_LEN_MIN, HEX_LEN_LONG_MAX); } + public static String longToHex(Long l, boolean bigEndian) { return prepareNumberHexString(l, bigEndian, false, HEX_LEN_MIN, HEX_LEN_LONG_MAX); } @@ -604,7 +641,7 @@ public class TbUtils { return Long.toString(number, radix); } return switch (radix) { - case MIN_RADIX -> Long.toBinaryString(number); + case MIN_RADIX -> Long.toBinaryString(number); case OCTAL_RADIX -> Long.toOctalString(number); case DEC_RADIX -> Long.toString(number); case HEX_RADIX -> prepareNumberHexString(number, bigEndian, pref, -1, -1); @@ -646,13 +683,13 @@ public class TbUtils { private static String removeLeadingZero_FF(String hex, Long number, int hexLenMax) { String hexWithoutZero = hex.replaceFirst("^0+(?!$)", ""); // Remove leading zeros except for the last one - hexWithoutZero = hexWithoutZero.length() % 2 > 0 ? "0" + hexWithoutZero : hexWithoutZero; + hexWithoutZero = hexWithoutZero.length() % 2 > 0 ? "0" + hexWithoutZero : hexWithoutZero; if (number >= 0) { return hexWithoutZero; } else { String hexWithoutZeroFF = hexWithoutZero.replaceFirst("^F+(?!$)", ""); - hexWithoutZeroFF = hexWithoutZeroFF.length() % 2 > 0 ? "F" + hexWithoutZeroFF : hexWithoutZeroFF; - if (hexWithoutZeroFF.length() > hexLenMax) { + hexWithoutZeroFF = hexWithoutZeroFF.length() % 2 > 0 ? "F" + hexWithoutZeroFF : hexWithoutZeroFF; + if (hexWithoutZeroFF.length() > hexLenMax) { return hexWithoutZeroFF.substring(hexWithoutZeroFF.length() - hexLenMax); } else if (hexWithoutZeroFF.length() == hexLenMax) { return hexWithoutZeroFF; @@ -668,7 +705,7 @@ public class TbUtils { public static String floatToHex(Float f, boolean bigEndian) { // Convert the float to its raw integer bits representation - int bits = Float.floatToRawIntBits(f); + int bits = Float.floatToIntBits(f); // Format the integer bits as a hexadecimal string String result = String.format("0x%08X", bits); @@ -719,7 +756,7 @@ public class TbUtils { if (offset > data.length) { throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!"); } - if (length > 4) { + if (length > BYTES_LEN_INT_MAX) { throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!"); } if (offset + length > data.length) { @@ -784,10 +821,33 @@ public class TbUtils { } public static float parseBytesToFloat(byte[] data, int offset, boolean bigEndian) { - byte[] bytesToNumber = prepareBytesToNumber(data, offset, 4, bigEndian); - return ByteBuffer.wrap(bytesToNumber).getFloat(); + return parseBytesToFloat(data, offset, BYTES_LEN_INT_MAX, bigEndian); + } + + public static float parseBytesToFloat(List data, int offset, int length, boolean bigEndian) { + return parseBytesToFloat(Bytes.toArray(data), offset, length, bigEndian); } + public static float parseBytesToFloat(byte[] data, int offset, int length, boolean bigEndian) { + byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); + if (length > BYTES_LEN_INT_MAX) { + throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!"); + } + if (bytesToNumber.length < BYTES_LEN_INT_MAX) { + byte[] extendedBytes = new byte[4]; + Arrays.fill(extendedBytes, (byte) 0); + System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length); + bytesToNumber = extendedBytes; + } + float floatValue = ByteBuffer.wrap(bytesToNumber).getFloat(); + if (!Float.isNaN(floatValue)){ + return floatValue; + } else { + long longValue = parseBytesToLong(bytesToNumber, offset, length, bigEndian); + BigDecimal bigDecimalValue = new BigDecimal(longValue); + return bigDecimalValue.floatValue(); + } + } public static double parseBytesToDouble(byte[] data, int offset) { return parseBytesToDouble(data, offset, true); @@ -931,14 +991,15 @@ public class TbUtils { } } - private static String prepareNumberString(String value) { - if (value != null) { + private static String prepareNumberString(String value, boolean bigEndian) { + if (StringUtils.isNotBlank(value)) { value = value.trim(); value = value.replace("0x", ""); value = value.replace("0X", ""); value = value.replace(",", "."); + return bigEndian ? value : reverseHexStringByOrder(value); } - return value; + return null; } private static int isValidStringAndRadix(String valueP, int radix, String value) { diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index 1d4e12c00e..1df1fc93cc 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -47,7 +47,7 @@ public class TbUtilsTest { private ExecutionContext ctx; - private final float floatVal = 29.29824f; + private final Float floatVal = 29.29824f; private final float floatValRev = -5.948442E7f; @@ -256,7 +256,7 @@ public class TbUtilsTest { @Test public void parseFloat() { - String floatValStr = "29.29824"; + String floatValStr = floatVal.toString(); Assertions.assertEquals(java.util.Optional.of(floatVal).get(), TbUtils.parseFloat(floatValStr)); String floatValHex = "41EA62CC"; Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseHexToFloat(floatValHex))); @@ -274,7 +274,7 @@ public class TbUtilsTest { } @Test - public void arseBytesToFloat() { + public void parseBytesToFloat() { byte[] floatValByte = {65, -22, 98, -52}; Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValByte, 0))); Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, false))); @@ -282,6 +282,33 @@ public class TbUtilsTest { List floatVaList = Bytes.asList(floatValByte); Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatVaList, 0))); Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatVaList, 0, false))); + + // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF} + floatValByte = new byte[] {-1, -1, -1, -1}; + float floatExpectedBe = 4294.9673f; + float floatExpectedLe = 4.2949673E9f; + float actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, false))); + + floatVaList = Bytes.asList(floatValByte); + actualBe = TbUtils.parseBytesToFloat(floatVaList, 0); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatVaList, 0, false))); + + // 2 143 289 344L == {0x7F, 0xC0, 0x00, 0x00} + floatValByte = new byte[] {0x7F, (byte) 0xC0, (byte) 0xFF, 0x00}; + floatExpectedBe = 2143.3547f; + floatExpectedLe = -3.984375f; + actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 2,false))); + + floatVaList = Bytes.asList(floatValByte); + floatExpectedLe = 8372479.0f; + actualBe = TbUtils.parseBytesToFloat(floatVaList, 0); + Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe/1000000)); + Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatVaList, 0, 3, false))); } @Test @@ -605,15 +632,51 @@ public class TbUtilsTest { @Test public void floatToHex_Test() { - Float value = 20.89f; - String expectedHex = "0x41A71EB8"; - String valueHexRev = "0xB81EA741"; + Float value = 123456789.00f; + String expectedHex = "0x4CEB79A3"; + String valueHexRev = "0xA379EB4C"; String actual = TbUtils.floatToHex(value); Assertions.assertEquals(expectedHex, actual); Float valueActual = TbUtils.parseHexToFloat(actual); Assertions.assertEquals(value, valueActual); valueActual = TbUtils.parseHexToFloat(valueHexRev, false); Assertions.assertEquals(value, valueActual); + value = 123456789.67f; + expectedHex = "0x4CEB79A3"; + valueHexRev = "0xA379EB4C"; + actual = TbUtils.floatToHex(value); + Assertions.assertEquals(expectedHex, actual); + valueActual = TbUtils.parseHexToFloat(actual); + Assertions.assertEquals(value, valueActual); + valueActual = TbUtils.parseHexToFloat(valueHexRev, false); + Assertions.assertEquals(value, valueActual); + value = 10.0f; + expectedHex = "0x41200000"; + valueHexRev = "0x00002041"; + actual = TbUtils.floatToHex(value); + Assertions.assertEquals(expectedHex, actual); + valueActual = TbUtils.parseHexToFloat(actual); + Assertions.assertEquals(value, valueActual); + valueActual = TbUtils.parseHexToFloat(valueHexRev, false); + Assertions.assertEquals(value, valueActual); + } + // If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f). + @Test + public void parseHexIntLongToFloat_Test() { + Float valueExpected = 10.0f; + Float valueActual = TbUtils.parseHexIntLongToFloat("0x0A", true); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseHexIntLongToFloat("0x0A", false); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseHexIntLongToFloat("0x00000A", true); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseHexIntLongToFloat("0x0A0000", false); + Assertions.assertEquals(valueExpected, valueActual); + valueExpected = 2570.0f; + valueActual = TbUtils.parseHexIntLongToFloat("0x000A0A", true); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseHexIntLongToFloat("0x0A0A00", false); + Assertions.assertEquals(valueExpected, valueActual); } @Test