From fa416be05771bdeefb7d660aea30a03767287060 Mon Sep 17 00:00:00 2001 From: pali Date: Sun, 1 Sep 2019 13:57:58 +0300 Subject: [PATCH] [bin2json] Binary to JSON converter initial contribution (#611) Signed-off-by: Pauli Anttila --- bom/compile/pom.xml | 8 + bom/openhab-core/pom.xml | 6 + bom/runtime/pom.xml | 8 + .../org.openhab.core.io.bin2json/.classpath | 27 ++ bundles/org.openhab.core.io.bin2json/.project | 23 ++ bundles/org.openhab.core.io.bin2json/NOTICE | 14 + .../org.openhab.core.io.bin2json/README.md | 76 ++++++ bundles/org.openhab.core.io.bin2json/pom.xml | 24 ++ .../openhab/core/io/bin2json/Bin2Json.java | 244 ++++++++++++++++++ .../core/io/bin2json/ConversionException.java | 42 +++ .../core/io/bin2json/Bin2JsonTest.java | 72 ++++++ bundles/pom.xml | 1 + .../openhab-core/src/main/feature/feature.xml | 7 + .../openhab-tp/src/main/feature/feature.xml | 5 + 14 files changed, 557 insertions(+) create mode 100644 bundles/org.openhab.core.io.bin2json/.classpath create mode 100644 bundles/org.openhab.core.io.bin2json/.project create mode 100644 bundles/org.openhab.core.io.bin2json/NOTICE create mode 100644 bundles/org.openhab.core.io.bin2json/README.md create mode 100644 bundles/org.openhab.core.io.bin2json/pom.xml create mode 100644 bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/Bin2Json.java create mode 100644 bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/ConversionException.java create mode 100644 bundles/org.openhab.core.io.bin2json/src/test/java/org/openhab/core/io/bin2json/Bin2JsonTest.java diff --git a/bom/compile/pom.xml b/bom/compile/pom.xml index 6748c8a95d..b72922de65 100644 --- a/bom/compile/pom.xml +++ b/bom/compile/pom.xml @@ -104,6 +104,14 @@ compile + + + com.igormaznitsa + jbbp + 1.4.1 + compile + + org.codehaus.jackson diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml index 1046fb14b2..8ac6f075b8 100644 --- a/bom/openhab-core/pom.xml +++ b/bom/openhab-core/pom.xml @@ -141,6 +141,12 @@ ${project.version} compile + + org.openhab.core.bundles + org.openhab.core.io.bin2json + ${project.version} + compile + org.openhab.core.bundles org.openhab.core.io.console diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index d4b7886962..16461a90ba 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -398,6 +398,14 @@ compile + + + com.igormaznitsa + jbbp + 1.4.1 + compile + + org.jmdns diff --git a/bundles/org.openhab.core.io.bin2json/.classpath b/bundles/org.openhab.core.io.bin2json/.classpath new file mode 100644 index 0000000000..3c5e7d1755 --- /dev/null +++ b/bundles/org.openhab.core.io.bin2json/.classpath @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.core.io.bin2json/.project b/bundles/org.openhab.core.io.bin2json/.project new file mode 100644 index 0000000000..04a66b08fa --- /dev/null +++ b/bundles/org.openhab.core.io.bin2json/.project @@ -0,0 +1,23 @@ + + + org.openhab.core.io.bin2json + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bundles/org.openhab.core.io.bin2json/NOTICE b/bundles/org.openhab.core.io.bin2json/NOTICE new file mode 100644 index 0000000000..6c17d0d8a4 --- /dev/null +++ b/bundles/org.openhab.core.io.bin2json/NOTICE @@ -0,0 +1,14 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-core + diff --git a/bundles/org.openhab.core.io.bin2json/README.md b/bundles/org.openhab.core.io.bin2json/README.md new file mode 100644 index 0000000000..9f7d378bcd --- /dev/null +++ b/bundles/org.openhab.core.io.bin2json/README.md @@ -0,0 +1,76 @@ +# Binary data to JSON format converter + +This bundle can be used to convert binary data to JSON format. + +This bundle utilize awesome Java Binary Block Parser. See more details about the library and parse rule syntax from page [Java Binary Block Parser](https://github.com/raydac/java-binary-block-parser">https://github.com/raydac/java-binary-block-parser). + + +Example usage: + +```java + +// @formatter:off +/* + * Frame format: + * +----+----+-----+-----+-----+-------+-----+----+-------+ + * | CC | 64 | F | | | | | | + * +----+----+-----+-----+-----+-------+-----+----+-------+ + * |<------------------ HDR ---------->| + * |<----- LEN ------>| + * |<-------------------- CRC -------------->| + * + */ + +String frameParserRule = + "ubyte cc;" // 0xCC, 204 + + "ubyte start;" // 0x64, 100 + + "ubyte flag;" // 0x85, 133 + + "ubyte destinationAddress;" // 0xFD, 253 + + "ubyte sourceAddress;" // 0x0A, 10 + + "ubyte dataLen;" // 0x0B, 11 + + "ubyte[dataLen] data;" // 0x2101A0010000030A040000, 33 1 160 1 0 0 3 10 4 0 0 + + "ushort crc;"; // 0x8C17, 35863 + + +final byte[] testdata = new byte[] { + (byte) 0xCC, + (byte) 0x64, + (byte) 0x85, + (byte) 0xFD, + (byte) 0x0A, + (byte) 0x0B, + (byte) 0x21, (byte) 0x01, (byte) 0xA0, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x0A, (byte) 0x04, (byte) 0x00, (byte) 0x00, + (byte) 0x8C, (byte) 0x17 }; +// @formatter:on + +JsonObject json = new Bin2Json(frameParserRule).convert(testdata); +logger.debug(json.toString()); +``` + +Outputs: + +```javascript +{ + "cc": 204, + "start": 100, + "flag": 133, + "destinationaddress": 253, + "sourceaddress": 10, + "datalen": 11, + "data": [ + 33, + 1, + 160, + 1, + 0, + 0, + 3, + 10, + 4, + 0, + 0 + ], + "crc": 35863 +} +``` + diff --git a/bundles/org.openhab.core.io.bin2json/pom.xml b/bundles/org.openhab.core.io.bin2json/pom.xml new file mode 100644 index 0000000000..4df5e9d44c --- /dev/null +++ b/bundles/org.openhab.core.io.bin2json/pom.xml @@ -0,0 +1,24 @@ + + + + 4.0.0 + + + org.openhab.core.bundles + org.openhab.core.reactor.bundles + 2.5.0-SNAPSHOT + + + org.openhab.core.io.bin2json + + openHAB Core :: Bundles :: Binary To JSON converter + + + + org.openhab.core.bundles + org.openhab.core + ${project.version} + + + + diff --git a/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/Bin2Json.java b/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/Bin2Json.java new file mode 100644 index 0000000000..a3d6b4c514 --- /dev/null +++ b/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/Bin2Json.java @@ -0,0 +1,244 @@ +/** + * Copyright (c) 2010-2019 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.bin2json; + +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.time.LocalDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.util.HexUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.igormaznitsa.jbbp.JBBPParser; +import com.igormaznitsa.jbbp.exceptions.JBBPException; +import com.igormaznitsa.jbbp.model.JBBPAbstractArrayField; +import com.igormaznitsa.jbbp.model.JBBPAbstractField; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayBit; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayBoolean; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayShort; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayUByte; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayUShort; +import com.igormaznitsa.jbbp.model.JBBPFieldBit; +import com.igormaznitsa.jbbp.model.JBBPFieldBoolean; +import com.igormaznitsa.jbbp.model.JBBPFieldByte; +import com.igormaznitsa.jbbp.model.JBBPFieldInt; +import com.igormaznitsa.jbbp.model.JBBPFieldLong; +import com.igormaznitsa.jbbp.model.JBBPFieldShort; +import com.igormaznitsa.jbbp.model.JBBPFieldStruct; +import com.igormaznitsa.jbbp.model.JBBPFieldUByte; +import com.igormaznitsa.jbbp.model.JBBPFieldUShort; + +/** + * This class converts binary data to JSON format. + * + * Parser rules follows Java Binary Block Parser syntax. + * + *

+ * + * See details from https://github.com/raydac/java-binary-block-parser + * + *

+ * Usage example: + * + *

+ * {@code
+ * JsonObject json = new Bin2Json("byte a; byte b; ubyte c;").convert("03FAFF");
+ * json.toString() = {"a":3,"b":-6,"c":255}
+ * 
+ * + * @author Pauli Anttila - Initial contribution + * + */ +@NonNullByDefault +public class Bin2Json { + + private final Logger logger = LoggerFactory.getLogger(Bin2Json.class); + + private final JBBPParser parser; + + /** + * Constructor. + * + * @param parserRule Binary data parser rule. + * @throws ConversionException if parse rule parsing fails. + */ + public Bin2Json(String parserRule) throws ConversionException { + try { + parser = JBBPParser.prepare(parserRule); + } catch (JBBPException e) { + throw new ConversionException(String.format("Illegal parser rule, reason: %s", e.getMessage(), e)); + } + } + + /** + * Convert {@link String} in hexadecimal string format to JSON object. + * + * @param hexString Data in hexadecimal string format. Example data: 03FAFF. + * @return Gson {@link JsonObject}. + * @throws ConversionException if an error occurs during conversion. + */ + public JsonObject convert(String hexString) throws ConversionException { + try { + return convert(HexUtils.hexToBytes(hexString)); + } catch (IllegalArgumentException e) { + throw new ConversionException(String.format("Illegal hexstring , reason: %s", e.getMessage(), e)); + } + } + + /** + * Convert byte array to JSON object. + * + * @param data Data in byte array format. + * @return Gson {@link JsonObject}. + * @throws ConversionException if an error occurs during conversion. + */ + public JsonObject convert(byte[] data) throws ConversionException { + try { + return convert(parser.parse(data)); + } catch (IOException e) { + throw new ConversionException(String.format("Unexpected error, reason: %s", e.getMessage(), e)); + } catch (JBBPException e) { + throw new ConversionException(String.format("Unexpected error, reason: %s", e.getMessage(), e)); + } + } + + /** + * Convert data from {@link InputStream} to JSON object. + * + * @param inputStream input stream where converted data is read. + * @return Gson {@link JsonObject}. + * @throws ConversionException if an error occurs during conversion. + */ + public JsonObject convert(InputStream inputStream) throws ConversionException { + try { + return convert(parser.parse(inputStream)); + } catch (IOException e) { + throw new ConversionException(String.format("Unexpected error, reason: %s", e.getMessage(), e)); + } catch (JBBPException e) { + throw new ConversionException(String.format("Unexpected error, reason: %s", e.getMessage(), e)); + } + } + + private JsonObject convert(JBBPFieldStruct data) throws ConversionException { + try { + LocalDateTime start = LocalDateTime.now(); + final JsonObject json = convertToJSon(data); + if (logger.isTraceEnabled()) { + Duration duration = Duration.between(start, LocalDateTime.now()); + logger.trace("Conversion time={}, json={}", duration, json); + } + return json; + } catch (JBBPException e) { + throw new ConversionException(String.format("Unexpected error, reason: %s", e.getMessage(), e)); + } + } + + private JsonObject convertToJSon(final JBBPAbstractField field) throws ConversionException { + return convertToJSon(null, field); + } + + private JsonObject convertToJSon(@Nullable final JsonObject json, final JBBPAbstractField field) + throws ConversionException { + JsonObject jsn = json == null ? new JsonObject() : json; + + final String fieldName = field.getFieldName() == null ? "nonamed" : field.getFieldName(); + if (field instanceof JBBPAbstractArrayField) { + final JsonArray jsonArray = new JsonArray(); + if (field instanceof JBBPFieldArrayBit) { + for (final byte b : ((JBBPFieldArrayBit) field).getArray()) { + jsonArray.add(new JsonPrimitive(b)); + } + } else if (field instanceof JBBPFieldArrayBoolean) { + for (final boolean b : ((JBBPFieldArrayBoolean) field).getArray()) { + jsonArray.add(new JsonPrimitive(b)); + } + } else if (field instanceof JBBPFieldArrayByte) { + for (final byte b : ((JBBPFieldArrayByte) field).getArray()) { + jsonArray.add(new JsonPrimitive(b)); + } + } else if (field instanceof JBBPFieldArrayInt) { + for (final int b : ((JBBPFieldArrayInt) field).getArray()) { + jsonArray.add(new JsonPrimitive(b)); + } + } else if (field instanceof JBBPFieldArrayLong) { + for (final long b : ((JBBPFieldArrayLong) field).getArray()) { + jsonArray.add(new JsonPrimitive(b)); + } + } else if (field instanceof JBBPFieldArrayShort) { + for (final short b : ((JBBPFieldArrayShort) field).getArray()) { + jsonArray.add(new JsonPrimitive(b)); + } + } else if (field instanceof JBBPFieldArrayStruct) { + final JBBPFieldArrayStruct array = (JBBPFieldArrayStruct) field; + for (int i = 0; i < array.size(); i++) { + jsonArray.add(convertToJSon(new JsonObject(), array.getElementAt(i))); + } + } else if (field instanceof JBBPFieldArrayUByte) { + for (final byte b : ((JBBPFieldArrayUByte) field).getArray()) { + jsonArray.add(new JsonPrimitive(b & 0xFF)); + } + } else if (field instanceof JBBPFieldArrayUShort) { + for (final short b : ((JBBPFieldArrayUShort) field).getArray()) { + jsonArray.add(new JsonPrimitive(b & 0xFFFF)); + } + } else { + throw new ConversionException(String.format("Unexpected field type '%s'", field)); + } + jsn.add(fieldName, jsonArray); + } else { + if (field instanceof JBBPFieldBit) { + jsn.addProperty(fieldName, ((JBBPFieldBit) field).getAsInt()); + } else if (field instanceof JBBPFieldBoolean) { + jsn.addProperty(fieldName, ((JBBPFieldBoolean) field).getAsBool()); + } else if (field instanceof JBBPFieldByte) { + jsn.addProperty(fieldName, ((JBBPFieldByte) field).getAsInt()); + } else if (field instanceof JBBPFieldInt) { + jsn.addProperty(fieldName, ((JBBPFieldInt) field).getAsInt()); + } else if (field instanceof JBBPFieldLong) { + jsn.addProperty(fieldName, ((JBBPFieldLong) field).getAsLong()); + } else if (field instanceof JBBPFieldShort) { + jsn.addProperty(fieldName, ((JBBPFieldShort) field).getAsInt()); + } else if (field instanceof JBBPFieldStruct) { + final JBBPFieldStruct struct = (JBBPFieldStruct) field; + final JsonObject obj = new JsonObject(); + for (final JBBPAbstractField f : struct.getArray()) { + convertToJSon(obj, f); + } + if (json == null) { + return obj; + } else { + jsn.add(fieldName, obj); + } + } else if (field instanceof JBBPFieldUByte) { + jsn.addProperty(fieldName, ((JBBPFieldUByte) field).getAsInt()); + } else if (field instanceof JBBPFieldUShort) { + jsn.addProperty(fieldName, ((JBBPFieldUShort) field).getAsInt()); + } else { + throw new ConversionException(String.format("Unexpected field '%s'", field)); + } + } + return jsn; + } +} diff --git a/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/ConversionException.java b/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/ConversionException.java new file mode 100644 index 0000000000..22d776c8a3 --- /dev/null +++ b/bundles/org.openhab.core.io.bin2json/src/main/java/org/openhab/core/io/bin2json/ConversionException.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2019 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.bin2json; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * {@link ConversionException} generic exception for errors which occurs during conversion. + * + * @author Pauli Anttila - Initial contribution + */ +@NonNullByDefault +public class ConversionException extends Exception { + + private static final long serialVersionUID = 1L; + + public ConversionException() { + super(); + } + + public ConversionException(String message) { + super(message); + } + + public ConversionException(String message, Throwable cause) { + super(message, cause); + } + + public ConversionException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.core.io.bin2json/src/test/java/org/openhab/core/io/bin2json/Bin2JsonTest.java b/bundles/org.openhab.core.io.bin2json/src/test/java/org/openhab/core/io/bin2json/Bin2JsonTest.java new file mode 100644 index 0000000000..b8a44851ef --- /dev/null +++ b/bundles/org.openhab.core.io.bin2json/src/test/java/org/openhab/core/io/bin2json/Bin2JsonTest.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2019 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.bin2json; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.junit.Test; + +import com.google.gson.JsonObject; + +/** + * Unit tests for {@link Bin2Json}. + * + * @author Pauli Anttila - Initial contribution + */ +public class Bin2JsonTest { + + @Test(expected = ConversionException.class) + public void testParserRuleError() throws ConversionException { + new Bin2Json("byte a byte b ubyte c;").convert(new byte[] { 3, 34, (byte) 255 }); + } + + @Test + public void testHexStringData() throws ConversionException { + JsonObject json = new Bin2Json("byte a; byte b; ubyte c;").convert("03FAFF"); + assertEquals("{\"a\":3,\"b\":-6,\"c\":255}", json.toString()); + } + + @Test(expected = ConversionException.class) + public void testHexStringDataError() throws ConversionException { + new Bin2Json("byte a; byte b; ubyte c;").convert("0322F"); + } + + @Test + public void testByteArrayData() throws ConversionException { + JsonObject json = new Bin2Json("ubyte length; ubyte[length] data;") + .convert(new byte[] { 4, 8, 33, 1, 2, 3, 4 }); + assertEquals("{\"length\":4,\"data\":[8,33,1,2]}", json.toString()); + } + + @Test(expected = ConversionException.class) + public void testByteArrayDataError() throws ConversionException { + new Bin2Json("byte a; byte b; ubyte c;").convert(new byte[] { 3, 34 }); + } + + @Test + public void testInputStreamData() throws ConversionException, IOException { + InputStream inputStream = new ByteArrayInputStream(new byte[] { 4, 8, 33, 1, 2, 3, 4 }); + JsonObject json = new Bin2Json("ubyte length; ubyte[length] data;").convert(inputStream); + assertEquals("{\"length\":4,\"data\":[8,33,1,2]}", json.toString()); + } + + @Test(expected = ConversionException.class) + public void testInputStreamDataError() throws ConversionException { + InputStream inputStream = new ByteArrayInputStream(new byte[] { 4, 8, 33 }); + new Bin2Json("ubyte length; ubyte[length] data;").convert(inputStream); + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index e80c93d48b..702070e236 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -48,6 +48,7 @@ org.openhab.core.boot org.openhab.core.compat1x org.openhab.core.karaf + org.openhab.core.io.bin2json org.openhab.core.io.console org.openhab.core.io.console.eclipse org.openhab.core.io.console.rfc147 diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml index 0bb6da4f0d..5b4ecdf1ae 100644 --- a/features/karaf/openhab-core/src/main/feature/feature.xml +++ b/features/karaf/openhab-core/src/main/feature/feature.xml @@ -125,6 +125,13 @@ + + openhab-core-base + mvn:org.openhab.core.bundles/org.openhab.core.io.bin2json/${project.version} + openhab.tp;filter:="(feature=jbbp)" + openhab.tp-jbbp + + openhab-core-base shell diff --git a/features/karaf/openhab-tp/src/main/feature/feature.xml b/features/karaf/openhab-tp/src/main/feature/feature.xml index 168d9be94f..c0028e7d0f 100644 --- a/features/karaf/openhab-tp/src/main/feature/feature.xml +++ b/features/karaf/openhab-tp/src/main/feature/feature.xml @@ -92,6 +92,11 @@ + + openhab.tp;feature=jbbp;version=1.4.1 + mvn:org.openhab.osgiify/com.igormaznitsa.jbbp/1.4.1 + + http mvn:org.glassfish.jersey.containers/jersey-container-servlet/2.22.2