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