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 d8aecb2f09..2884ba6214 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 @@ -194,6 +194,22 @@ public class TbUtils { byte[].class, int.class, int.class))); parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat", byte[].class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat", + List.class))); + parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat", + List.class, int.class))); + parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat", + List.class, int.class, int.class))); + parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat", + List.class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat", + byte[].class))); + parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat", + byte[].class, int.class))); + parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat", + byte[].class, int.class, int.class))); + parserConfig.addImport("parseBytesIntToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesIntToFloat", + byte[].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", @@ -218,6 +234,22 @@ public class TbUtils { byte[].class, int.class, int.class))); parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble", byte[].class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", + List.class))); + parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", + List.class, int.class))); + parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", + List.class, int.class, int.class))); + parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", + List.class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", + byte[].class))); + parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", + byte[].class, int.class))); + parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", + byte[].class, int.class, int.class))); + parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", + byte[].class, int.class, int.class, boolean.class))); parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", double.class, int.class))); parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", @@ -292,6 +324,9 @@ public class TbUtils { String.class))); parserConfig.addImport("isHexadecimal", new MethodStub(TbUtils.class.getMethod("isHexadecimal", String.class))); + parserConfig.addImport("byteArrayToExecutionArrayList", new MethodStub(TbUtils.class.getMethod("byteArrayToExecutionArrayList", + ExecutionContext.class, byte[].class))); + } public static String btoa(String input) { @@ -753,7 +788,7 @@ public class TbUtils { long bits = Double.doubleToRawLongBits(d); // Format the integer bits as a hexadecimal string - String result = String.format("0x%16X", bits); + String result = String.format("0x%016X", bits); return bigEndian ? result : reverseHexStringByOrder(result); } @@ -770,15 +805,15 @@ public class TbUtils { } public static int parseBytesToInt(List data) { - return parseBytesToInt(Bytes.toArray(data)); + return parseBytesToInt(data, 0); } public static int parseBytesToInt(List data, int offset) { - return parseBytesToInt(Bytes.toArray(data), offset); + return parseBytesToInt(data, offset, validateLength(data.size(), offset, BYTES_LEN_INT_MAX)); } public static int parseBytesToInt(List data, int offset, int length) { - return parseBytesToInt(Bytes.toArray(data), offset, length); + return parseBytesToInt(data, offset, length, true); } public static int parseBytesToInt(List data, int offset, int length, boolean bigEndian) { @@ -798,15 +833,7 @@ public class TbUtils { } public static int parseBytesToInt(byte[] data, int offset, int length, boolean bigEndian) { - if (offset > data.length) { - throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!"); - } - if (length > BYTES_LEN_INT_MAX) { - throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!"); - } - if (offset + length > data.length) { - throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); - } + validationNumberByLength(data, offset, length, BYTES_LEN_INT_MAX); var bb = ByteBuffer.allocate(4); if (!bigEndian) { bb.order(ByteOrder.LITTLE_ENDIAN); @@ -818,15 +845,15 @@ public class TbUtils { } public static long parseBytesToLong(List data) { - return parseBytesToLong(Bytes.toArray(data)); + return parseBytesToLong(data, 0); } public static long parseBytesToLong(List data, int offset) { - return parseBytesToLong(Bytes.toArray(data), offset); + return parseBytesToLong(data, offset, validateLength(data.size(), offset, BYTES_LEN_LONG_MAX)); } public static long parseBytesToLong(List data, int offset, int length) { - return parseBytesToLong(Bytes.toArray(data), offset, length); + return parseBytesToLong(data, offset, length, true); } public static long parseBytesToLong(List data, int offset, int length, boolean bigEndian) { @@ -846,15 +873,7 @@ public class TbUtils { } public static long parseBytesToLong(byte[] data, int offset, int length, boolean bigEndian) { - if (offset > data.length) { - throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!"); - } - if (length > BYTES_LEN_LONG_MAX) { - throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_LONG_MAX + " bytes is allowed!"); - } - if (offset + length > data.length) { - throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); - } + validationNumberByLength(data, offset, length, BYTES_LEN_LONG_MAX); var bb = ByteBuffer.allocate(BYTES_LEN_LONG_MAX); if (!bigEndian) { bb.order(ByteOrder.LITTLE_ENDIAN); @@ -866,15 +885,15 @@ public class TbUtils { } public static float parseBytesToFloat(List data) { - return parseBytesToFloat(Bytes.toArray(data), 0); + return parseBytesToFloat(data, 0); } public static float parseBytesToFloat(List data, int offset) { - return parseBytesToFloat(Bytes.toArray(data), offset, BYTES_LEN_INT_MAX); + return parseBytesToFloat(data, offset, validateLength(data.size(), offset, BYTES_LEN_INT_MAX)); } public static float parseBytesToFloat(List data, int offset, int length) { - return parseBytesToFloat(Bytes.toArray(data), offset, length, true); + return parseBytesToFloat(data, offset, length, true); } public static float parseBytesToFloat(List data, int offset, int length, boolean bigEndian) { @@ -886,7 +905,7 @@ public class TbUtils { } public static float parseBytesToFloat(byte[] data, int offset) { - return parseBytesToFloat(data, offset, BYTES_LEN_INT_MAX); + return parseBytesToFloat(data, offset, validateLength(data.length, offset, BYTES_LEN_INT_MAX)); } public static float parseBytesToFloat(byte[] data, int offset, int length) { @@ -894,39 +913,65 @@ public class TbUtils { } public static float parseBytesToFloat(byte[] data, int offset, int length, boolean bigEndian) { - if (length > BYTES_LEN_INT_MAX) { - throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_INT_MAX + " bytes is allowed!"); - } - if (offset + length > data.length) { - throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); - } - byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); - if (bytesToNumber.length < BYTES_LEN_INT_MAX) { - byte[] extendedBytes = new byte[BYTES_LEN_INT_MAX]; - Arrays.fill(extendedBytes, (byte) 0); - System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length); - bytesToNumber = extendedBytes; + var bb = ByteBuffer.allocate(BYTES_LEN_INT_MAX); + if (!bigEndian) { + bb.order(ByteOrder.LITTLE_ENDIAN); } - float floatValue = ByteBuffer.wrap(bytesToNumber).getFloat(); - if (!Float.isNaN(floatValue)) { - return floatValue; - } else { - long longValue = parseBytesToLong(bytesToNumber, 0, BYTES_LEN_INT_MAX); - BigDecimal bigDecimalValue = new BigDecimal(longValue); - return bigDecimalValue.floatValue(); + bb.position(bigEndian ? BYTES_LEN_INT_MAX - length : 0); + bb.put(data, offset, length); + bb.position(0); + float floatValue = bb.getFloat(); + if (Float.isNaN(floatValue)) { + throw new NumberFormatException("byte[] 0x" + bytesToHex(data) + " is a Not-a-Number (NaN) value"); } + return floatValue; + } + + public static float parseBytesIntToFloat(List data) { + return parseBytesIntToFloat(data, 0); + } + + public static float parseBytesIntToFloat(List data, int offset) { + return parseBytesIntToFloat(data, offset, validateLength(data.size(), offset, BYTES_LEN_INT_MAX)); + } + + public static float parseBytesIntToFloat(List data, int offset, int length) { + return parseBytesIntToFloat(data, offset, length, true); + } + + public static float parseBytesIntToFloat(List data, int offset, int length, boolean bigEndian) { + return parseBytesIntToFloat(Bytes.toArray(data), offset, length, bigEndian); + } + + public static float parseBytesIntToFloat(byte[] data) { + return parseBytesIntToFloat(data, 0); + } + + public static float parseBytesIntToFloat(byte[] data, int offset) { + return parseBytesIntToFloat(data, offset, validateLength(data.length, offset, BYTES_LEN_INT_MAX)); + } + + public static float parseBytesIntToFloat(byte[] data, int offset, int length) { + return parseBytesIntToFloat(data, offset, length, true); + } + + public static float parseBytesIntToFloat(byte[] data, int offset, int length, boolean bigEndian) { + byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian, BYTES_LEN_INT_MAX); + long longValue = parseBytesToLong(bytesToNumber, 0, length); + BigDecimal bigDecimalValue = new BigDecimal(longValue); + return bigDecimalValue.floatValue(); } public static double parseBytesToDouble(List data) { - return parseBytesToDouble(Bytes.toArray(data)); + return parseBytesToDouble(data, 0); } public static double parseBytesToDouble(List data, int offset) { - return parseBytesToDouble(Bytes.toArray(data), offset); + return parseBytesToDouble(data, offset, validateLength(data.size(), offset, BYTES_LEN_LONG_MAX)); } public static double parseBytesToDouble(List data, int offset, int length) { - return parseBytesToDouble(Bytes.toArray(data), offset, length); + return parseBytesToDouble(data, offset, length, true); } public static double parseBytesToDouble(List data, int offset, int length, boolean bigEndian) { @@ -938,7 +983,7 @@ public class TbUtils { } public static double parseBytesToDouble(byte[] data, int offset) { - return parseBytesToDouble(data, offset, BYTES_LEN_LONG_MAX); + return parseBytesToDouble(data, offset, validateLength(data.length, offset, BYTES_LEN_LONG_MAX)); } public static double parseBytesToDouble(byte[] data, int offset, int length) { @@ -946,36 +991,58 @@ public class TbUtils { } public static double parseBytesToDouble(byte[] data, int offset, int length, boolean bigEndian) { - if (length > BYTES_LEN_LONG_MAX) { - throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_LONG_MAX + " bytes is allowed!"); - } - if (offset + length > data.length) { - throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); - } - byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian); - if (bytesToNumber.length < BYTES_LEN_LONG_MAX) { - byte[] extendedBytes = new byte[BYTES_LEN_LONG_MAX]; - Arrays.fill(extendedBytes, (byte) 0); - System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length); - bytesToNumber = extendedBytes; + var bb = ByteBuffer.allocate(BYTES_LEN_LONG_MAX); + if (!bigEndian) { + bb.order(ByteOrder.LITTLE_ENDIAN); } - double doubleValue = ByteBuffer.wrap(bytesToNumber).getDouble(); - if (!Double.isNaN(doubleValue)) { - return doubleValue; - } else { - BigInteger bigInt = new BigInteger(1, bytesToNumber); - BigDecimal bigDecimalValue = new BigDecimal(bigInt); - return bigDecimalValue.doubleValue(); + bb.position(bigEndian ? BYTES_LEN_LONG_MAX - length : 0); + bb.put(data, offset, length); + bb.position(0); + double doubleValue = bb.getDouble(); + if (Double.isNaN(doubleValue)) { + throw new NumberFormatException("byte[] 0x" + bytesToHex(data) + " is a Not-a-Number (NaN) value"); } + return doubleValue; } - private static byte[] prepareBytesToNumber(byte[] data, int offset, int length, boolean bigEndian) { - if (offset > data.length) { - throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!"); - } - if ((offset + length) > data.length) { - throw new IllegalArgumentException("Default length is always " + length + " bytes. Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); - } + public static double parseBytesLongToDouble(List data) { + return parseBytesLongToDouble(data, 0); + } + + public static double parseBytesLongToDouble(List data, int offset) { + return parseBytesLongToDouble(data, offset, validateLength(data.size(), offset, BYTES_LEN_LONG_MAX)); + } + + public static double parseBytesLongToDouble(List data, int offset, int length) { + return parseBytesLongToDouble(data, offset, length, true); + } + + public static double parseBytesLongToDouble(List data, int offset, int length, boolean bigEndian) { + return parseBytesLongToDouble(Bytes.toArray(data), offset, length, bigEndian); + } + + public static double parseBytesLongToDouble(byte[] data) { + return parseBytesLongToDouble(data, 0); + } + + public static double parseBytesLongToDouble(byte[] data, int offset) { + return parseBytesLongToDouble(data, offset, validateLength(data.length, offset, BYTES_LEN_LONG_MAX)); + } + + public static double parseBytesLongToDouble(byte[] data, int offset, int length) { + return parseBytesLongToDouble(data, offset, length, true); + } + + public static double parseBytesLongToDouble(byte[] data, int offset, int length, boolean bigEndian) { + byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian, BYTES_LEN_LONG_MAX); + BigInteger bigInt = new BigInteger(1, bytesToNumber); + BigDecimal bigDecimalValue = new BigDecimal(bigInt); + return bigDecimalValue.doubleValue(); + } + + private static byte[] prepareBytesToNumber(byte[] data, int offset, int length, boolean bigEndian, + int bytesLenMax) { + validationNumberByLength(data, offset, length, bytesLenMax); byte[] dataBytesArray = Arrays.copyOfRange(data, offset, (offset + length)); if (!bigEndian) { ArrayUtils.reverse(dataBytesArray); @@ -1168,6 +1235,15 @@ public class TbUtils { return str.matches("^-?(0[xX])?[0-9a-fA-F]+$") ? HEX_RADIX : -1; } + public static List byteArrayToExecutionArrayList(ExecutionContext ctx, byte[] byteArray) { + List byteList = new ArrayList<>(); + for (byte b : byteArray) { + byteList.add(b); + } + List list = new ExecutionArrayList(byteList, ctx); + return list; + } + private static byte isValidIntegerToByte(Integer val) { if (val > 255 || val < -128) { throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " + @@ -1194,5 +1270,23 @@ public class TbUtils { String result = reversedHex.toString(); return isHexPref ? "0x" + result : result; } + + private static void validationNumberByLength(byte[] data, int offset, int length, int bytesLenMax) { + if (offset > data.length) { + throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!"); + } + + if (offset + length > data.length) { + throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); + } + + if (length > bytesLenMax) { + throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + bytesLenMax + " bytes is allowed!"); + } + } + + private static int validateLength(int dataLength, int offset, int bytesLenMax) { + return (dataLength < offset) ? dataLength : Math.min((dataLength - offset), bytesLenMax); + } } 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 a7292d4280..c9ee9a977c 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 @@ -276,7 +276,10 @@ public class TbUtilsTest { @Test public void parseBytesToFloat() { - byte[] floatValByte = {65, -22, 98, -52}; + byte[] floatValByte = {0x0A}; + Assertions.assertEquals(0, Float.compare(1.4E-44f, TbUtils.parseBytesToFloat(floatValByte))); + + floatValByte = new byte[]{65, -22, 98, -52}; Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValByte, 0))); Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, 4, false))); @@ -284,40 +287,101 @@ public class TbUtilsTest { Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValList, 0))); Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValList, 0, 4, false))); - // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF} - floatValByte = new byte[]{-1, -1, -1, -1}; - float floatExpectedBe = 4294.9673f; - float floatExpectedLe = 4.2949673E9f; + + // 1.1803216E8f == 0x4CE120E4 + floatValByte = new byte[]{0x4C, (byte) 0xE1, (byte) 0x20, (byte) 0xE4}; + float floatExpectedBe = 118.03216f; 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, 4, false))); - floatValList = Bytes.asList(floatValByte); actualBe = TbUtils.parseBytesToFloat(floatValList, 0); Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); - Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 4, 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)); + float floatExpectedLe = 8.0821E-41f; Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 2, false))); + floatExpectedBe = 2.7579E-41f; + Assertions.assertEquals(0, Float.compare(floatExpectedBe, TbUtils.parseBytesToFloat(floatValByte, 0, 2))); - floatValList = Bytes.asList(floatValByte); - floatExpectedLe = 4.2908055E9f; - actualBe = TbUtils.parseBytesToFloat(floatValList, 0); - Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000)); + + floatExpectedLe = 3.019557E-39f; Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 3, false))); + + // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF} + floatValByte = new byte[]{-1, -1, -1, -1}; + String message = "is a Not-a-Number (NaN) value"; + try { + TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); + Assertions.fail("Should throw NumberFormatException"); + } catch (RuntimeException e) { + Assertions.assertTrue(e.getMessage().contains(message)); + } + // "01752B0367FA000500010488 FFFFFFFF FFFFFFFF 33"; String intToHexBe = "01752B0367FA000500010488FFFFFFFFFFFFFFFF33"; - floatExpectedLe = 4294.9673f; floatValList = TbUtils.hexToBytes(ctx, intToHexBe); - float actualLe = TbUtils.parseBytesToFloat(floatValList, 12, 4, false); - Assertions.assertEquals(0, Float.compare(floatExpectedLe, actualLe / 1000000)); - actualLe = TbUtils.parseBytesToFloat(floatValList, 12 + 4, 4, false); - Assertions.assertEquals(0, Float.compare(floatExpectedLe, actualLe / 1000000)); + try { + TbUtils.parseBytesToFloat(floatValList, 12, 4, false); + Assertions.fail("Should throw NumberFormatException"); + } catch (RuntimeException e) { + Assertions.assertTrue(e.getMessage().contains(message)); + } + } + + @Test + public void parseBytesIntToFloat() { + byte[] intValByte = {0x00, 0x00, 0x00, 0x0A}; + Float valueExpected = 10.0f; + Float valueActual = TbUtils.parseBytesIntToFloat(intValByte, 3, 1, true); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseBytesIntToFloat(intValByte, 3, 1, false); + Assertions.assertEquals(valueExpected, valueActual); + + valueActual = TbUtils.parseBytesIntToFloat(intValByte, 2, 2, true); + Assertions.assertEquals(valueExpected, valueActual); + valueExpected = 2560.0f; + valueActual = TbUtils.parseBytesIntToFloat(intValByte, 2, 2, false); + Assertions.assertEquals(valueExpected, valueActual); + + valueExpected = 10.0f; + valueActual = TbUtils.parseBytesIntToFloat(intValByte, 0, 4, true); + Assertions.assertEquals(valueExpected, valueActual); + valueExpected = 1.6777216E8f; + valueActual = TbUtils.parseBytesIntToFloat(intValByte, 0, 4, false); + Assertions.assertEquals(valueExpected, valueActual); + + String dataAT101 = "0x01756403671B01048836BF7701F000090722050000"; + List byteAT101 = TbUtils.hexToBytes(ctx, dataAT101); + float latitudeExpected = 24.62495f; + int offset = 9; + valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset, 4, false); + Assertions.assertEquals(latitudeExpected, valueActual / 1000000); + + float longitudeExpected = 118.030576f; + valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset + 4, 4, false); + Assertions.assertEquals(longitudeExpected, valueActual / 1000000); + + valueExpected = 9.185175E8f; + valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset); + Assertions.assertEquals(valueExpected, valueActual); + // 0x36BF + valueExpected = 14015.0f; + valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset, 2); + Assertions.assertEquals(valueExpected, valueActual); + // 0xBF36 + valueExpected = 48950.0f; + valueActual = TbUtils.parseBytesIntToFloat(byteAT101, offset, 2, false); + Assertions.assertEquals(valueExpected, valueActual); + + valueExpected = 0.0f; + valueActual = TbUtils.parseBytesIntToFloat(byteAT101, byteAT101.size()); + Assertions.assertEquals(valueExpected, valueActual); + + try { + TbUtils.parseBytesIntToFloat(byteAT101, byteAT101.size() + 1); + Assertions.fail("Should throw NumberFormatException"); + } catch (RuntimeException e) { + Assertions.assertTrue(e.getMessage().contains("is out of bounds for array with length:")); + } } @Test @@ -390,7 +454,10 @@ public class TbUtilsTest { @Test public void parseBytesToDouble() { - byte[] doubleValByte = {64, -101, 4, -79, 12, -78, -107, -22}; + byte[] doubleValByte = {0x0A}; + Assertions.assertEquals(0, Double.compare(4.9E-323, TbUtils.parseBytesToDouble(doubleValByte))); + + doubleValByte = new byte[]{64, -101, 4, -79, 12, -78, -107, -22}; Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValByte, 0))); Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false))); @@ -398,34 +465,61 @@ public class TbUtilsTest { Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValList, 0))); Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValList, 0, 8, false))); - // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} - doubleValByte = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1}; - double doubleExpectedBe = 18446.744073709553d; - double doubleExpectedLe = 1.8446744073709552E19d; - double actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true); - Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe / 1000000000000000L)); - Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false))); - - doubleValList = Bytes.asList(doubleValByte); - Assertions.assertEquals(0, Double.compare(doubleExpectedBe, TbUtils.parseBytesToDouble(doubleValList, 0) / 1000000000000000L)); - Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, 8, false))); - doubleValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00, 0x7F, (byte) 0xC0, (byte) 0xFF, 0x00}; - doubleExpectedBe = 2387013.651780523d; - doubleExpectedLe = 7.234601680440024E-304d; - actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true); + double doubleExpectedBe = 2387013.651780523d; + double doubleExpectedLe = 7.234601680440024E-304d; + double actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true); BigDecimal bigDecimal = new BigDecimal(actualBe); // We move the decimal point to the left by 301 positions actualBe = bigDecimal.movePointLeft(301).doubleValue(); Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe)); Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false))); + doubleValList = Bytes.asList(doubleValByte); - doubleExpectedLe = 5.828674572203954E303d; actualBe = TbUtils.parseBytesToDouble(doubleValList, 0); bigDecimal = new BigDecimal(actualBe); actualBe = bigDecimal.movePointLeft(301).doubleValue(); Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe)); - Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, 5, false))); + doubleExpectedLe = 26950.174646662283d; + double actualLe = TbUtils.parseBytesToDouble(doubleValList, 0, 5, false); + bigDecimal = new BigDecimal(actualLe); + actualLe = bigDecimal.movePointRight(316).doubleValue(); + Assertions.assertEquals(0, Double.compare(doubleExpectedLe, actualLe)); + + // 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + doubleValByte = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1}; + String message = "is a Not-a-Number (NaN) value"; + try { + TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true); + Assertions.fail("Should throw NumberFormatException"); + } catch (RuntimeException e) { + Assertions.assertTrue(e.getMessage().contains(message)); + } + } + + @Test + public void parseBytesLongToDouble() { + byte[] longValByte = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A}; + Double valueExpected = 10.0d; + Double valueActual = TbUtils.parseBytesLongToDouble(longValByte); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseBytesLongToDouble(longValByte, 7, 1, true); + Assertions.assertEquals(valueExpected, valueActual); + valueActual = TbUtils.parseBytesLongToDouble(longValByte, 7, 1, false); + Assertions.assertEquals(valueExpected, valueActual); + + valueActual = TbUtils.parseBytesLongToDouble(longValByte, 6, 2, true); + Assertions.assertEquals(valueExpected, valueActual); + valueExpected = 2560.0d; + valueActual = TbUtils.parseBytesLongToDouble(longValByte, 6, 2, false); + Assertions.assertEquals(valueExpected, valueActual); + + valueExpected = 10.0d; + valueActual = TbUtils.parseBytesLongToDouble(longValByte, 0, 8, true); + Assertions.assertEquals(valueExpected, valueActual); + valueExpected = 7.2057594037927936E17d; + valueActual = TbUtils.parseBytesLongToDouble(longValByte, 0, 8, false); + Assertions.assertEquals(valueExpected, valueActual); } @Test @@ -717,6 +811,13 @@ public class TbUtilsTest { Assertions.assertEquals(value, valueActual); valueActual = TbUtils.parseHexToFloat(valueHexRev, false); Assertions.assertEquals(value, valueActual); + + String valueHex = "0x0000000A"; + float expectedValue = 1.4E-44f; + valueActual = TbUtils.parseHexToFloat(valueHex); + Assertions.assertEquals(expectedValue, valueActual); + actual = TbUtils.floatToHex(expectedValue); + Assertions.assertEquals(valueHex, actual); } // If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f). @@ -748,6 +849,13 @@ public class TbUtilsTest { actual = TbUtils.doubleToHex(doubleVal, false); String expectedHexRev = "0xEA95B20CB1049B40"; Assertions.assertEquals(expectedHexRev, actual); + + String valueHex = "0x000000000000000A"; + Double expectedValue = 4.9E-323; + valueActual = TbUtils.parseHexToDouble(valueHex); + Assertions.assertEquals(expectedValue, valueActual); + actual = TbUtils.doubleToHex(expectedValue); + Assertions.assertEquals(valueHex, actual); } @Test