6 changed files with 700 additions and 174 deletions
@ -0,0 +1,199 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.mqtt.util.sparkplug; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
||||
|
import org.thingsboard.server.gen.transport.mqtt.SparkplugBProto; |
||||
|
|
||||
|
import java.math.BigInteger; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 10.01.23 |
||||
|
*/ |
||||
|
|
||||
|
@Slf4j |
||||
|
public enum MetricDataType { |
||||
|
|
||||
|
// Basic Types
|
||||
|
Int8(1, Byte.class), |
||||
|
Int16(2, Short.class), |
||||
|
Int32(3, Integer.class), |
||||
|
Int64(4, Long.class), |
||||
|
UInt8(5, Short.class), |
||||
|
UInt16(6, Integer.class), |
||||
|
UInt32(7, Long.class), |
||||
|
UInt64(8, BigInteger.class), |
||||
|
Float(9, Float.class), |
||||
|
Double(10, Double.class), |
||||
|
Boolean(11, Boolean.class), |
||||
|
String(12, String.class), |
||||
|
DateTime(13, Date.class), |
||||
|
Text(14, String.class), |
||||
|
|
||||
|
// Custom Types for Metrics
|
||||
|
UUID(15, String.class), |
||||
|
DataSet(16, SparkplugBProto.Payload.DataSet.class), |
||||
|
Bytes(17, byte[].class), |
||||
|
File(18, SparkplugMetricUtil.File.class), |
||||
|
Template(19, SparkplugBProto.Payload.Template.class), |
||||
|
|
||||
|
// PropertyValue Types (20 and 21) are NOT metric datatypes
|
||||
|
|
||||
|
// Array Types
|
||||
|
Int8Array(22, Byte[].class), |
||||
|
Int16Array(23, Short[].class), |
||||
|
Int32Array(24, Integer[].class), |
||||
|
Int64Array(25, Long[].class), |
||||
|
UInt8Array(26, Short[].class), |
||||
|
UInt16Array(27, Integer[].class), |
||||
|
UInt32Array(28, Long[].class), |
||||
|
UInt64Array(29, BigInteger[].class), |
||||
|
FloatArray(30, Float[].class), |
||||
|
DoubleArray(31, Double[].class), |
||||
|
BooleanArray(32, Boolean[].class), |
||||
|
StringArray(33, String[].class), |
||||
|
DateTimeArray(34, Date[].class), |
||||
|
|
||||
|
// Unknown
|
||||
|
Unknown(0, Object.class); |
||||
|
|
||||
|
private Class<?> clazz = null; |
||||
|
private int intValue = 0; |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
* |
||||
|
* @param intValue the integer value of this {@link MetricDataType} |
||||
|
* @param clazz the {@link Class} type associated with this {@link MetricDataType} |
||||
|
*/ |
||||
|
private MetricDataType(int intValue, Class<?> clazz) { |
||||
|
this.intValue = intValue; |
||||
|
this.clazz = clazz; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks the type of a specified value against the specified {@link MetricDataType} |
||||
|
* |
||||
|
* @param value the {@link Object} value to check against the {@link MetricDataType} |
||||
|
* @throws AdaptorException if the value is not a valid type for the given {@link MetricDataType} |
||||
|
*/ |
||||
|
public void checkType(Object value) throws AdaptorException { |
||||
|
if (value != null && !clazz.isAssignableFrom(value.getClass())) { |
||||
|
String msgError = "Failed type check - " + clazz + " != " + ((value != null) ? value.getClass().toString() : "null"); |
||||
|
log.debug(msgError); |
||||
|
throw new AdaptorException(msgError); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns an integer representation of the data type. |
||||
|
* |
||||
|
* @return an integer representation of the data type. |
||||
|
*/ |
||||
|
public int toIntValue() { |
||||
|
return this.intValue; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Converts the integer representation of the data type into a {@link MetricDataType} instance. |
||||
|
* |
||||
|
* @param i the integer representation of the data type. |
||||
|
* @return a {@link MetricDataType} instance. |
||||
|
*/ |
||||
|
public static MetricDataType fromInteger(int i) { |
||||
|
switch (i) { |
||||
|
case 1: |
||||
|
return Int8; |
||||
|
case 2: |
||||
|
return Int16; |
||||
|
case 3: |
||||
|
return Int32; |
||||
|
case 4: |
||||
|
return Int64; |
||||
|
case 5: |
||||
|
return UInt8; |
||||
|
case 6: |
||||
|
return UInt16; |
||||
|
case 7: |
||||
|
return UInt32; |
||||
|
case 8: |
||||
|
return UInt64; |
||||
|
case 9: |
||||
|
return Float; |
||||
|
case 10: |
||||
|
return Double; |
||||
|
case 11: |
||||
|
return Boolean; |
||||
|
case 12: |
||||
|
return String; |
||||
|
case 13: |
||||
|
return DateTime; |
||||
|
case 14: |
||||
|
return Text; |
||||
|
case 15: |
||||
|
return UUID; |
||||
|
case 16: |
||||
|
return DataSet; |
||||
|
case 17: |
||||
|
return Bytes; |
||||
|
case 18: |
||||
|
return File; |
||||
|
case 19: |
||||
|
return Template; |
||||
|
case 22: |
||||
|
return Int8Array; |
||||
|
case 23: |
||||
|
return Int16Array; |
||||
|
case 24: |
||||
|
return Int32Array; |
||||
|
case 25: |
||||
|
return Int64Array; |
||||
|
case 26: |
||||
|
return UInt8Array; |
||||
|
case 27: |
||||
|
return UInt16Array; |
||||
|
case 28: |
||||
|
return UInt32Array; |
||||
|
case 29: |
||||
|
return UInt64Array; |
||||
|
case 30: |
||||
|
return FloatArray; |
||||
|
case 31: |
||||
|
return DoubleArray; |
||||
|
case 32: |
||||
|
return BooleanArray; |
||||
|
case 33: |
||||
|
return StringArray; |
||||
|
case 34: |
||||
|
return DateTimeArray; |
||||
|
default: |
||||
|
return Unknown; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the class type for this DataType |
||||
|
* |
||||
|
* @return the class type for this DataType |
||||
|
*/ |
||||
|
public Class<?> getClazz() { |
||||
|
return clazz; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,244 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.mqtt.util.sparkplug; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
||||
|
import com.fasterxml.jackson.databind.ser.std.FileSerializer; |
||||
|
import com.google.gson.Gson; |
||||
|
import com.google.gson.GsonBuilder; |
||||
|
import com.google.gson.JsonArray; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.codec.binary.Hex; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.gen.transport.mqtt.SparkplugBProto; |
||||
|
|
||||
|
import java.math.BigInteger; |
||||
|
import java.nio.ByteBuffer; |
||||
|
import java.nio.ByteOrder; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
/** |
||||
|
* Provides utility methods for SparkplugB MQTT Payload Metric. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class SparkplugMetricUtil { |
||||
|
|
||||
|
public static Optional<TransportProtos.KeyValueProto> getFromSparkplugBMetricToKeyValueProto(String key, SparkplugBProto.Payload.Metric protoMetric) throws ThingsboardException { |
||||
|
// Check if the null flag has been set indicating that the value is null
|
||||
|
if (protoMetric.getIsNull()) { |
||||
|
return null; |
||||
|
} |
||||
|
// Otherwise convert the value based on the type
|
||||
|
int metricType = protoMetric.getDatatype(); |
||||
|
switch (MetricDataType.fromInteger(metricType)) { |
||||
|
case Boolean: |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.BOOLEAN_V) |
||||
|
.setBoolV(protoMetric.getBooleanValue()).build()); |
||||
|
case DateTime: |
||||
|
case Int64: |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.LONG_V) |
||||
|
.setLongV(protoMetric.getLongValue()).build()); |
||||
|
case File: |
||||
|
String filename = protoMetric.getMetadata().getFileName(); |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key + "_" + filename).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV(Hex.encodeHexString((protoMetric.getBytesValue().toByteArray()))).build()); |
||||
|
case Float: |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.LONG_V) |
||||
|
.setLongV((long) protoMetric.getFloatValue()).build()); |
||||
|
case Double: |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.DOUBLE_V) |
||||
|
.setDoubleV(protoMetric.getDoubleValue()).build()); |
||||
|
case Int8: |
||||
|
case Int16: |
||||
|
case Int32: |
||||
|
case UInt16: |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.LONG_V) |
||||
|
.setLongV(protoMetric.getIntValue()).build()); |
||||
|
case UInt32: |
||||
|
if (protoMetric.hasIntValue()) { |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.LONG_V) |
||||
|
.setLongV(protoMetric.getIntValue()).build()); |
||||
|
} else if (protoMetric.hasLongValue()) { |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.LONG_V) |
||||
|
.setLongV(protoMetric.getLongValue()).build()); |
||||
|
} else { |
||||
|
log.error("Invalid value for UInt32 datatype"); |
||||
|
throw new ThingsboardException("Invalid value for UInt32 datatype " + metricType, ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
case UInt64: |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.DOUBLE_V) |
||||
|
.setDoubleV((new BigInteger(Long.toUnsignedString(protoMetric.getLongValue()))).longValue()).build()); |
||||
|
case String: |
||||
|
case Text: |
||||
|
case UUID: |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV(protoMetric.getStringValue()).build()); |
||||
|
case Bytes: |
||||
|
case Int8Array: |
||||
|
case Int16Array: |
||||
|
case Int32Array: |
||||
|
case Int64Array: |
||||
|
case UInt8Array: |
||||
|
case UInt16Array: |
||||
|
case UInt32Array: |
||||
|
case UInt64Array: |
||||
|
case FloatArray: |
||||
|
case DoubleArray: |
||||
|
case BooleanArray: |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV(Hex.encodeHexString(protoMetric.getBytesValue().toByteArray())).build()); |
||||
|
case DataSet: |
||||
|
SparkplugBProto.Payload.DataSet protoDataSet = protoMetric.getDatasetValue(); |
||||
|
//TODO
|
||||
|
// Build the and create the DataSet
|
||||
|
/** |
||||
|
return new SparkplugBProto.Payload.DataSet.Builder(protoDataSet.getNumOfColumns()).addColumnNames(protoDataSet.getColumnsList()) |
||||
|
.addTypes(convertDataSetDataTypes(protoDataSet.getTypesList())) |
||||
|
.addRows(convertDataSetRows(protoDataSet.getRowsList(), protoDataSet.getTypesList())) |
||||
|
.createDataSet(); |
||||
|
**/ |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV(protoDataSet.toString()).build()); |
||||
|
case Template: |
||||
|
//TODO
|
||||
|
// Build the and create the Template
|
||||
|
SparkplugBProto.Payload.Template protoTemplate = protoMetric.getTemplateValue(); |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV( protoTemplate.toString()).build()); |
||||
|
case StringArray: |
||||
|
ByteBuffer stringByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()); |
||||
|
List<String> stringList = new ArrayList<>(); |
||||
|
stringByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
StringBuilder sb = new StringBuilder(); |
||||
|
while (stringByteBuffer.hasRemaining()) { |
||||
|
byte b = stringByteBuffer.get(); |
||||
|
if (b == (byte) 0) { |
||||
|
stringList.add(sb.toString()); |
||||
|
sb = new StringBuilder(); |
||||
|
} else { |
||||
|
|
||||
|
sb.append((char) b); |
||||
|
} |
||||
|
} |
||||
|
String st = StringUtils.join(stringList, "|"); |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV(st).build()); |
||||
|
case DateTimeArray: |
||||
|
ByteBuffer dateTimeByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()); |
||||
|
List<Long> dateTimeList = new ArrayList<Long>(); |
||||
|
dateTimeByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
||||
|
while (dateTimeByteBuffer.hasRemaining()) { |
||||
|
long longValue = dateTimeByteBuffer.getLong(); |
||||
|
dateTimeList.add(longValue); |
||||
|
} |
||||
|
Gson gson = new GsonBuilder().create(); |
||||
|
JsonArray dateTimeArray = gson.toJsonTree(dateTimeList).getAsJsonArray(); |
||||
|
return Optional.of(TransportProtos.KeyValueProto.newBuilder().setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setStringV(dateTimeArray.toString()).build()); |
||||
|
case Unknown: |
||||
|
default: |
||||
|
throw new ThingsboardException("Failed to decode: Unknown MetricDataType " + metricType, ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@JsonIgnoreProperties( |
||||
|
value = {"fileName"}) |
||||
|
@JsonSerialize( |
||||
|
using = FileSerializer.class) |
||||
|
public class File { |
||||
|
|
||||
|
private String fileName; |
||||
|
private byte[] bytes; |
||||
|
|
||||
|
/** |
||||
|
* Default Constructor |
||||
|
*/ |
||||
|
public File() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
* |
||||
|
* @param fileName the full file name path |
||||
|
* @param bytes the array of bytes that represent the contents of the file |
||||
|
*/ |
||||
|
public File(String fileName, byte[] bytes) { |
||||
|
super(); |
||||
|
this.fileName = fileName == null |
||||
|
? null |
||||
|
: fileName.replace("/", System.getProperty("file.separator")).replace("\\", |
||||
|
System.getProperty("file.separator")); |
||||
|
this.bytes = Arrays.copyOf(bytes, bytes.length); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the full filename path |
||||
|
* |
||||
|
* @return the full filename path |
||||
|
*/ |
||||
|
public String getFileName() { |
||||
|
return fileName; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sets the full filename path |
||||
|
* |
||||
|
* @param fileName the full filename path |
||||
|
*/ |
||||
|
public void setFileName(String fileName) { |
||||
|
this.fileName = fileName; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the bytes that represent the contents of the file |
||||
|
* |
||||
|
* @return the bytes that represent the contents of the file |
||||
|
*/ |
||||
|
public byte[] getBytes() { |
||||
|
return bytes; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sets the bytes that represent the contents of the file |
||||
|
* |
||||
|
* @param bytes the bytes that represent the contents of the file |
||||
|
*/ |
||||
|
public void setBytes(byte[] bytes) { |
||||
|
this.bytes = bytes; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
StringBuilder builder = new StringBuilder(); |
||||
|
builder.append("File [fileName="); |
||||
|
builder.append(fileName); |
||||
|
builder.append(", bytes="); |
||||
|
builder.append(Arrays.toString(bytes)); |
||||
|
builder.append("]"); |
||||
|
return builder.toString(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue