committed by
GitHub
16 changed files with 1802 additions and 174 deletions
@ -0,0 +1,256 @@ |
|||||
|
/** |
||||
|
* 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.sparkplug; |
||||
|
|
||||
|
import com.google.protobuf.ByteString; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.paho.mqttv5.client.IMqttToken; |
||||
|
import org.eclipse.paho.mqttv5.common.packet.MqttConnAck; |
||||
|
import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode; |
||||
|
import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage; |
||||
|
import org.junit.Assert; |
||||
|
import org.thingsboard.server.common.data.TransportPayloadType; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.gen.transport.mqtt.SparkplugBProto; |
||||
|
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; |
||||
|
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; |
||||
|
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient; |
||||
|
import org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType; |
||||
|
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMetricUtil; |
||||
|
|
||||
|
import java.io.ByteArrayOutputStream; |
||||
|
import java.io.DataOutputStream; |
||||
|
import java.io.IOException; |
||||
|
import java.io.ObjectOutputStream; |
||||
|
import java.nio.ByteBuffer; |
||||
|
import java.util.Calendar; |
||||
|
|
||||
|
import static org.eclipse.paho.mqttv5.common.packet.MqttWireMessage.MESSAGE_TYPE_CONNACK; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 12.01.23 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public abstract class AbstractMqttV5ClientSparkplugTest extends AbstractMqttIntegrationTest { |
||||
|
|
||||
|
protected MqttV5TestClient client; |
||||
|
protected Calendar calendar = Calendar.getInstance(); |
||||
|
|
||||
|
protected static final String NAMESPACE = "spBv1.0"; |
||||
|
protected static final String groupId = "SparkplugBGroupId"; |
||||
|
protected static final String edgeNode = "SparkpluBNode"; |
||||
|
protected static final String keysBdSeq = "bdSeq"; |
||||
|
protected static final String alias = "Failed Post Telemetry node proto payload. SparkplugMessageType "; |
||||
|
protected String deviceId = "Test Sparkplug B Device"; |
||||
|
protected int bdSeq = 0; |
||||
|
protected int seq = 0; |
||||
|
protected static final long PUBLISH_TS_DELTA_MS = 86400000;// Publish start TS <-> 24h
|
||||
|
|
||||
|
|
||||
|
public void beforeSparkplugTest() throws Exception { |
||||
|
MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() |
||||
|
.gatewayName("Test Connect Sparkplug client node") |
||||
|
.isSparkPlug(true) |
||||
|
.transportPayloadType(TransportPayloadType.PROTOBUF) |
||||
|
.build(); |
||||
|
processBeforeTest(configProperties); |
||||
|
} |
||||
|
|
||||
|
public void processClientWithCorrectNodeAccess() throws Exception { |
||||
|
this.client = new MqttV5TestClient(); |
||||
|
MqttWireMessage response = clientWithCorrectNodeAccessToken(client); |
||||
|
Assert.assertEquals(MESSAGE_TYPE_CONNACK, response.getType()); |
||||
|
MqttConnAck connAckMsg = (MqttConnAck) response; |
||||
|
Assert.assertEquals(MqttReturnCode.RETURN_CODE_SUCCESS, connAckMsg.getReturnCode()); |
||||
|
} |
||||
|
|
||||
|
protected SparkplugBProto.Payload.Metric createMetric(Object value, TsKvEntry tsKvEntry, MetricDataType metricDataType) throws ThingsboardException { |
||||
|
SparkplugBProto.Payload.Metric metric = SparkplugBProto.Payload.Metric.newBuilder() |
||||
|
.setTimestamp(tsKvEntry.getTs()) |
||||
|
.setName(tsKvEntry.getKey()) |
||||
|
.setDatatype(metricDataType.toIntValue()) |
||||
|
.build(); |
||||
|
switch (metricDataType) { |
||||
|
case Int8: |
||||
|
case Int16: |
||||
|
case UInt8: |
||||
|
case UInt16: |
||||
|
int valueMetric = Integer.valueOf(String.valueOf(value)); |
||||
|
return metric.toBuilder().setIntValue(valueMetric).build(); |
||||
|
case Int32: |
||||
|
case UInt32: |
||||
|
if (value instanceof Long) { |
||||
|
return metric.toBuilder().setLongValue((long) value).build(); |
||||
|
} else { |
||||
|
return metric.toBuilder().setIntValue((int)value).build(); |
||||
|
} |
||||
|
case Int64: |
||||
|
case UInt64: |
||||
|
case DateTime: |
||||
|
return metric.toBuilder().setLongValue((long) value).build(); |
||||
|
case Float: |
||||
|
return metric.toBuilder().setFloatValue((float) value).build(); |
||||
|
case Double: |
||||
|
return metric.toBuilder().setDoubleValue((double) value).build(); |
||||
|
case Boolean: |
||||
|
return metric.toBuilder().setBooleanValue((boolean) value).build(); |
||||
|
case String: |
||||
|
case Text: |
||||
|
case UUID: |
||||
|
return metric.toBuilder().setStringValue((String) value).build(); |
||||
|
case DataSet: |
||||
|
return metric.toBuilder().setDatasetValue((SparkplugBProto.Payload.DataSet) value).build(); |
||||
|
case Bytes: |
||||
|
case Int8Array: |
||||
|
ByteString byteString = ByteString.copyFrom((byte[]) value); |
||||
|
return metric.toBuilder().setBytesValue(byteString).build(); |
||||
|
case Int16Array: |
||||
|
case UInt8Array: |
||||
|
byte[] int16Array = shortArrayToByteArray((short[]) value); |
||||
|
ByteString byteInt16Array = ByteString.copyFrom((int16Array)); |
||||
|
return metric.toBuilder().setBytesValue(byteInt16Array).build(); |
||||
|
case Int32Array: |
||||
|
case UInt16Array: |
||||
|
case Int64Array: |
||||
|
case UInt32Array: |
||||
|
case UInt64Array: |
||||
|
case DateTimeArray: |
||||
|
if (value instanceof int[]) { |
||||
|
byte[] int32Array = integerArrayToByteArray((int[]) value); |
||||
|
ByteString byteInt32Array = ByteString.copyFrom((int32Array)); |
||||
|
return metric.toBuilder().setBytesValue(byteInt32Array).build(); |
||||
|
} else { |
||||
|
byte[] int64Array = longArrayToByteArray((long[]) value); |
||||
|
ByteString byteInt64Array = ByteString.copyFrom((int64Array)); |
||||
|
return metric.toBuilder().setBytesValue(byteInt64Array).build(); |
||||
|
} |
||||
|
case DoubleArray: |
||||
|
byte[] doubleArray = doublArrayToByteArray((double[]) value); |
||||
|
ByteString byteDoubleArray = ByteString.copyFrom(doubleArray); |
||||
|
return metric.toBuilder().setBytesValue(byteDoubleArray).build(); |
||||
|
case FloatArray: |
||||
|
byte[] floatArray = floatArrayToByteArray((float[]) value); |
||||
|
ByteString byteFloatArray = ByteString.copyFrom(floatArray); |
||||
|
return metric.toBuilder().setBytesValue(byteFloatArray).build(); |
||||
|
case BooleanArray: |
||||
|
byte[] booleanArray = booleanArrayToByteArray((boolean[]) value); |
||||
|
ByteString byteBooleanArray = ByteString.copyFrom(booleanArray); |
||||
|
return metric.toBuilder().setBytesValue(byteBooleanArray).build(); |
||||
|
case StringArray: |
||||
|
byte[] stringArray = stringArrayToByteArray((String[]) value); |
||||
|
ByteString byteStringArray = ByteString.copyFrom(stringArray); |
||||
|
return metric.toBuilder().setBytesValue(byteStringArray).build(); |
||||
|
case File: |
||||
|
SparkplugMetricUtil.File file = (SparkplugMetricUtil.File) value; |
||||
|
ByteString byteFileString = ByteString.copyFrom(file.getBytes()); |
||||
|
return metric.toBuilder().setBytesValue(byteFileString).build(); |
||||
|
case Template: |
||||
|
return metric.toBuilder().setTemplateValue((SparkplugBProto.Payload.Template) value).build(); |
||||
|
case Unknown: |
||||
|
throw new ThingsboardException("Invalid value for MetricDataType " + metricDataType.name(), ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
return metric; |
||||
|
} |
||||
|
|
||||
|
private byte[] shortArrayToByteArray(short[] inputs) { |
||||
|
ByteBuffer bb = ByteBuffer.allocate(inputs.length * 2); |
||||
|
for (short d : inputs) { |
||||
|
bb.putShort(d); |
||||
|
} |
||||
|
return bb.array(); |
||||
|
} |
||||
|
|
||||
|
private byte[] integerArrayToByteArray(int[] inputs) { |
||||
|
ByteBuffer bb = ByteBuffer.allocate(inputs.length * 4); |
||||
|
for (int d : inputs) { |
||||
|
bb.putInt(d); |
||||
|
} |
||||
|
return bb.array(); |
||||
|
} |
||||
|
|
||||
|
private byte[] longArrayToByteArray(long[] inputs) { |
||||
|
ByteBuffer bb = ByteBuffer.allocate(inputs.length * 8); |
||||
|
for (long d : inputs) { |
||||
|
bb.putLong(d); |
||||
|
} |
||||
|
return bb.array(); |
||||
|
} |
||||
|
|
||||
|
private byte[] doublArrayToByteArray(double[] inputs) { |
||||
|
ByteBuffer bb = ByteBuffer.allocate(inputs.length * 8); |
||||
|
for (double d : inputs) { |
||||
|
bb.putDouble(d); |
||||
|
} |
||||
|
return bb.array(); |
||||
|
} |
||||
|
|
||||
|
private byte[] floatArrayToByteArray(float[] inputs) throws ThingsboardException { |
||||
|
ByteArrayOutputStream bas = new ByteArrayOutputStream(); |
||||
|
DataOutputStream ds = new DataOutputStream(bas); |
||||
|
for (float f : inputs) { |
||||
|
try { |
||||
|
ds.writeFloat(f); |
||||
|
} catch (IOException e) { |
||||
|
throw new ThingsboardException("Invalid value float ", ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
} |
||||
|
return bas.toByteArray(); |
||||
|
} |
||||
|
|
||||
|
private byte[] booleanArrayToByteArray(boolean[] inputs) { |
||||
|
byte[] toReturn = new byte[inputs.length]; |
||||
|
for (int entry = 0; entry < toReturn.length; entry++) { |
||||
|
toReturn[entry] = (byte) (inputs[entry]?1:0); |
||||
|
} |
||||
|
return toReturn; |
||||
|
} |
||||
|
|
||||
|
private byte[] stringArrayToByteArray(String[] inputs) throws ThingsboardException { |
||||
|
final ByteArrayOutputStream bas = new ByteArrayOutputStream(); |
||||
|
try { |
||||
|
final ObjectOutputStream os = new ObjectOutputStream(bas); |
||||
|
os.writeObject(inputs); |
||||
|
os.flush(); |
||||
|
os.close(); |
||||
|
} catch (Exception e) { |
||||
|
throw new ThingsboardException("Invalid value float ", ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
return bas.toByteArray(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private MqttWireMessage clientWithCorrectNodeAccessToken(MqttV5TestClient client) throws Exception { |
||||
|
IMqttToken connectionResult = client.connectAndWait(gatewayAccessToken); |
||||
|
return connectionResult.getResponse(); |
||||
|
} |
||||
|
|
||||
|
protected long getBdSeqNum() throws Exception { |
||||
|
if (bdSeq == 256) { |
||||
|
bdSeq = 0; |
||||
|
} |
||||
|
return bdSeq++; |
||||
|
} |
||||
|
|
||||
|
protected long getSeqNum() throws Exception { |
||||
|
if (seq == 256) { |
||||
|
seq = 0; |
||||
|
} |
||||
|
return seq++; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
/** |
||||
|
* 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.sparkplug.connection; |
||||
|
|
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.paho.mqttv5.client.IMqttToken; |
||||
|
import org.eclipse.paho.mqttv5.client.MqttConnectionOptions; |
||||
|
import org.eclipse.paho.mqttv5.common.MqttMessage; |
||||
|
import org.eclipse.paho.mqttv5.common.packet.MqttConnAck; |
||||
|
import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode; |
||||
|
import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage; |
||||
|
import org.junit.Assert; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.LongDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.gen.transport.mqtt.SparkplugBProto; |
||||
|
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient; |
||||
|
import org.thingsboard.server.transport.mqtt.sparkplug.AbstractMqttV5ClientSparkplugTest; |
||||
|
import org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType; |
||||
|
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
import java.util.Set; |
||||
|
import java.util.HashSet; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
import java.util.concurrent.atomic.AtomicReference; |
||||
|
|
||||
|
import static org.awaitility.Awaitility.await; |
||||
|
import static org.eclipse.paho.mqttv5.common.packet.MqttWireMessage.MESSAGE_TYPE_CONNACK; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int32; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int64; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 12.01.23 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public abstract class AbstractMqttV5ClientSparkplugConnectionTest extends AbstractMqttV5ClientSparkplugTest { |
||||
|
|
||||
|
protected void processClientWithCorrectNodeAccessTokenTest() throws Exception { |
||||
|
processClientWithCorrectNodeAccess(); |
||||
|
} |
||||
|
|
||||
|
protected void processClientWithCorrectNodeAccessTokenWithNdeathTest() throws Exception { |
||||
|
long ts = calendar.getTimeInMillis()-PUBLISH_TS_DELTA_MS; |
||||
|
long value = bdSeq = 0; |
||||
|
MetricDataType metricDataType = Int64; |
||||
|
TsKvEntry tsKvEntryBdSecOriginal = new BasicTsKvEntry(ts, new LongDataEntry(keysBdSeq, value)); |
||||
|
|
||||
|
SparkplugBProto.Payload.Builder deathPayload = SparkplugBProto.Payload.newBuilder() |
||||
|
.setTimestamp(calendar.getTimeInMillis()); |
||||
|
deathPayload.addMetrics(createMetric(value, tsKvEntryBdSecOriginal, metricDataType)); |
||||
|
|
||||
|
MqttWireMessage response = clientWithCorrectNodeAccessTokenWithNDEATH(deathPayload.build().toByteArray()); |
||||
|
|
||||
|
Assert.assertEquals(MESSAGE_TYPE_CONNACK, response.getType()); |
||||
|
|
||||
|
MqttConnAck connAckMsg = (MqttConnAck) response; |
||||
|
|
||||
|
Assert.assertEquals(MqttReturnCode.RETURN_CODE_SUCCESS, connAckMsg.getReturnCode()); |
||||
|
|
||||
|
String keys = SparkplugMessageType.NDEATH.name() + " " + keysBdSeq; |
||||
|
TsKvEntry expectedTsKvEntry = new BasicTsKvEntry(ts, new LongDataEntry(keys, value)); |
||||
|
AtomicReference<ListenableFuture<Optional<TsKvEntry>>> finalFuture = new AtomicReference<>(); |
||||
|
await(alias + SparkplugMessageType.NDEATH.name()) |
||||
|
.atMost(40, TimeUnit.SECONDS) |
||||
|
.until(() -> { |
||||
|
finalFuture.set(tsService.findLatest(tenantId, savedGateway.getId(), keys)); |
||||
|
return finalFuture.get().get().isPresent(); |
||||
|
}); |
||||
|
TsKvEntry actualTsKvEntry = finalFuture.get().get().get(); |
||||
|
Assert.assertEquals(expectedTsKvEntry, actualTsKvEntry); |
||||
|
} |
||||
|
|
||||
|
protected void processClientWithCorrectAccessTokenCreatedDevices(int cntDevices) throws Exception { |
||||
|
processClientWithCorrectNodeAccess(); |
||||
|
long ts = calendar.getTimeInMillis(); |
||||
|
MetricDataType metricDataType = Int32; |
||||
|
Set<String> deviceIds = new HashSet<>(); |
||||
|
String keys = "Device Metric int32"; |
||||
|
int valueDeviceInt32 = 1024; |
||||
|
TsKvEntry expectedTsKvEntryDeviceInt32 = new BasicTsKvEntry(ts, new LongDataEntry(keys, Integer.toUnsignedLong(valueDeviceInt32))); |
||||
|
SparkplugBProto.Payload.Metric metric = createMetric(valueDeviceInt32, expectedTsKvEntryDeviceInt32, metricDataType); |
||||
|
for (int i=0; i < cntDevices; i++ ) { |
||||
|
SparkplugBProto.Payload.Builder payloadBirthDevice = SparkplugBProto.Payload.newBuilder() |
||||
|
.setTimestamp(calendar.getTimeInMillis()) |
||||
|
.setSeq(getSeqNum()); |
||||
|
String deviceName = deviceId + "_" + i; |
||||
|
|
||||
|
payloadBirthDevice.addMetrics(metric); |
||||
|
if (client.isConnected()) { |
||||
|
client.publish(NAMESPACE + "/" + groupId + "/" + SparkplugMessageType.DBIRTH.name() + "/" + edgeNode + "/" + deviceName, |
||||
|
payloadBirthDevice.build().toByteArray(), 0, false); |
||||
|
deviceIds.add(deviceName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Assert.assertEquals(cntDevices, deviceIds.size()); |
||||
|
|
||||
|
for (String deviceName: deviceIds) { |
||||
|
AtomicReference<Device> device = new AtomicReference<>(); |
||||
|
await(alias + "find device [" + deviceName + "] after crete") |
||||
|
.atMost(40, TimeUnit.SECONDS) |
||||
|
.until(() -> { |
||||
|
device.set(doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class)); |
||||
|
return device.get() != null; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private MqttWireMessage clientWithCorrectNodeAccessTokenWithNDEATH(byte[] deathBytes) throws Exception { |
||||
|
this.client = new MqttV5TestClient(); |
||||
|
MqttConnectionOptions options = new MqttConnectionOptions(); |
||||
|
options.setUserName(gatewayAccessToken); |
||||
|
if (deathBytes != null) { |
||||
|
String topic = NAMESPACE + "/" + groupId + "/" + SparkplugMessageType.NDEATH.name() + "/" + edgeNode; |
||||
|
MqttMessage msg = new MqttMessage(); |
||||
|
msg.setId(0); |
||||
|
msg.setPayload(deathBytes); |
||||
|
options.setWill(topic, msg); |
||||
|
} |
||||
|
IMqttToken connectionResult = client.connect(options); |
||||
|
return connectionResult.getResponse(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
/** |
||||
|
* 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.sparkplug.connection; |
||||
|
|
||||
|
import org.eclipse.paho.mqttv5.common.MqttException; |
||||
|
import org.junit.After; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 12.01.23 |
||||
|
*/ |
||||
|
@DaoSqlTest |
||||
|
public class MqttV5ClientSparkplugBConnectionTest extends AbstractMqttV5ClientSparkplugConnectionTest { |
||||
|
|
||||
|
@Before |
||||
|
public void beforeTest() throws Exception { |
||||
|
beforeSparkplugTest(); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void afterTest() throws MqttException { |
||||
|
if (client.isConnected()) { |
||||
|
client.disconnect(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testClientWithCorrectAccessToken() throws Exception { |
||||
|
processClientWithCorrectNodeAccessTokenTest(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testClientWithCorrectAccessTokenWithNDEATH() throws Exception { |
||||
|
processClientWithCorrectNodeAccessTokenWithNdeathTest(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testClientWithCorrectAccessTokenCreatedOneDevice() throws Exception { |
||||
|
processClientWithCorrectAccessTokenCreatedDevices(1); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testClientWithCorrectAccessTokenCreatedTwoDevice() throws Exception { |
||||
|
processClientWithCorrectAccessTokenCreatedDevices(2); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,505 @@ |
|||||
|
/** |
||||
|
* 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.sparkplug.timeseries; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.node.ArrayNode; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.junit.Assert; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.JsonDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.LongDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.StringDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.gen.transport.mqtt.SparkplugBProto; |
||||
|
import org.thingsboard.server.transport.mqtt.sparkplug.AbstractMqttV5ClientSparkplugTest; |
||||
|
import org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType; |
||||
|
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType; |
||||
|
|
||||
|
import java.math.BigDecimal; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.concurrent.ThreadLocalRandom; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
import java.util.concurrent.atomic.AtomicReference; |
||||
|
|
||||
|
import static org.awaitility.Awaitility.await; |
||||
|
import static org.thingsboard.common.util.JacksonUtil.newArrayNode; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.BooleanArray; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Bytes; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.DateTimeArray; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.DoubleArray; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.FloatArray; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int16; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int16Array; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int32; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int32Array; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int64; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int64Array; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int8; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.Int8Array; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.StringArray; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.UInt16; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.UInt16Array; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.UInt32; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.UInt32Array; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.UInt64; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.UInt64Array; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.UInt8; |
||||
|
import static org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType.UInt8Array; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 12.01.23 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public abstract class AbstractMqttV5ClientSparkplugTelemetryTest extends AbstractMqttV5ClientSparkplugTest { |
||||
|
|
||||
|
protected ThreadLocalRandom random = ThreadLocalRandom.current(); |
||||
|
|
||||
|
protected void processClientWithCorrectAccessTokenPublishNBIRTH() throws Exception { |
||||
|
processClientWithCorrectNodeAccess(); |
||||
|
List<String> listKeys = new ArrayList<>(); |
||||
|
SparkplugBProto.Payload.Builder payloadBirthNode = SparkplugBProto.Payload.newBuilder() |
||||
|
.setTimestamp(calendar.getTimeInMillis()); |
||||
|
long ts = calendar.getTimeInMillis() - PUBLISH_TS_DELTA_MS; |
||||
|
long valueBdSec = getBdSeqNum(); |
||||
|
MetricDataType metricDataType = Int64; |
||||
|
TsKvEntry tsKvEntryBdSecOriginal = new BasicTsKvEntry(ts, new LongDataEntry(keysBdSeq, valueBdSec)); |
||||
|
payloadBirthNode.addMetrics(createMetric(valueBdSec, tsKvEntryBdSecOriginal, metricDataType)); |
||||
|
listKeys.add(SparkplugMessageType.NBIRTH.name() + " " + keysBdSeq); |
||||
|
|
||||
|
String keys = "Node Control/Rebirth"; |
||||
|
boolean valueRebirth = false; |
||||
|
metricDataType = MetricDataType.Boolean; |
||||
|
TsKvEntry expectedSsKvEntryRebirth = new BasicTsKvEntry(ts, new BooleanDataEntry(keys, valueRebirth)); |
||||
|
payloadBirthNode.addMetrics(createMetric(valueRebirth, expectedSsKvEntryRebirth, metricDataType)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "Node Metric int32"; |
||||
|
int valueNodeInt32 = 1024; |
||||
|
metricDataType = Int32; |
||||
|
TsKvEntry expectedSsKvEntryNodeInt32 = new BasicTsKvEntry(ts, new LongDataEntry(keys, Integer.toUnsignedLong(valueNodeInt32))); |
||||
|
payloadBirthNode.addMetrics(createMetric(valueNodeInt32, expectedSsKvEntryNodeInt32, metricDataType)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
client.publish(NAMESPACE + "/" + groupId + "/" + SparkplugMessageType.NBIRTH.name() + "/" + edgeNode, |
||||
|
payloadBirthNode.build().toByteArray(), 0, false); |
||||
|
|
||||
|
AtomicReference<ListenableFuture<List<TsKvEntry>>> finalFuture = new AtomicReference<>(); |
||||
|
await(alias + SparkplugMessageType.NBIRTH.name()) |
||||
|
.atMost(40, TimeUnit.SECONDS) |
||||
|
.until(() -> { |
||||
|
finalFuture.set(tsService.findLatest(tenantId, savedGateway.getId(), listKeys)); |
||||
|
return !finalFuture.get().get().isEmpty(); |
||||
|
}); |
||||
|
Assert.assertEquals(listKeys.size(), finalFuture.get().get().size()); |
||||
|
} |
||||
|
|
||||
|
protected void processClientWithCorrectAccessTokenPublishNCMDReBirth() throws Exception { |
||||
|
processClientWithCorrectNodeAccess(); |
||||
|
SparkplugBProto.Payload.Builder payloadBirthNode = SparkplugBProto.Payload.newBuilder() |
||||
|
.setTimestamp(calendar.getTimeInMillis()); |
||||
|
List<String> listKeys = new ArrayList<>(); |
||||
|
long ts = calendar.getTimeInMillis() - PUBLISH_TS_DELTA_MS; |
||||
|
long valueBdSec = getBdSeqNum(); |
||||
|
MetricDataType metricDataType = Int64; |
||||
|
TsKvEntry tsKvEntryBdSecOriginal = new BasicTsKvEntry(ts, new LongDataEntry(keysBdSeq, valueBdSec)); |
||||
|
payloadBirthNode.addMetrics(createMetric(valueBdSec, tsKvEntryBdSecOriginal, metricDataType)); |
||||
|
listKeys.add(SparkplugMessageType.NCMD.name() + " " + keysBdSeq); |
||||
|
|
||||
|
String keys = "Node Control/Rebirth"; |
||||
|
boolean valueRebirth = true; |
||||
|
metricDataType = MetricDataType.Boolean; |
||||
|
TsKvEntry expectedSsKvEntryRebirth = new BasicTsKvEntry(ts, new BooleanDataEntry(keys, valueRebirth)); |
||||
|
payloadBirthNode.addMetrics(createMetric(valueRebirth, expectedSsKvEntryRebirth, metricDataType)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
client.publish(NAMESPACE + "/" + groupId + "/" + SparkplugMessageType.NCMD.name() + "/" + edgeNode, |
||||
|
payloadBirthNode.build().toByteArray(), 0, false); |
||||
|
|
||||
|
AtomicReference<ListenableFuture<List<TsKvEntry>>> finalFuture = new AtomicReference<>(); |
||||
|
await(alias + SparkplugMessageType.NCMD.name()) |
||||
|
.atMost(40, TimeUnit.SECONDS) |
||||
|
.until(() -> { |
||||
|
finalFuture.set(tsService.findLatest(tenantId, savedGateway.getId(), listKeys)); |
||||
|
return !finalFuture.get().get().isEmpty(); |
||||
|
}); |
||||
|
Assert.assertEquals(listKeys.size(), finalFuture.get().get().size()); |
||||
|
} |
||||
|
|
||||
|
protected void processClientWithCorrectAccessTokenPushNodeMetricBuildPrimitiveSimple() throws Exception { |
||||
|
processClientWithCorrectNodeAccess(); |
||||
|
|
||||
|
String messageTypeName = SparkplugMessageType.NDATA.name(); |
||||
|
List<String> listKeys = new ArrayList<>(); |
||||
|
List<TsKvEntry> listTsKvEntry = new ArrayList<>(); |
||||
|
|
||||
|
SparkplugBProto.Payload.Builder ndataPayload = SparkplugBProto.Payload.newBuilder() |
||||
|
.setTimestamp(calendar.getTimeInMillis()) |
||||
|
.setSeq(getSeqNum()); |
||||
|
long ts = calendar.getTimeInMillis() - PUBLISH_TS_DELTA_MS; |
||||
|
|
||||
|
createdAddMetricValuePrimitiveTsKv(listTsKvEntry, listKeys, ndataPayload, ts); |
||||
|
|
||||
|
if (client.isConnected()) { |
||||
|
client.publish(NAMESPACE + "/" + groupId + "/" + messageTypeName + "/" + edgeNode, |
||||
|
ndataPayload.build().toByteArray(), 0, false); |
||||
|
} |
||||
|
|
||||
|
AtomicReference<ListenableFuture<List<TsKvEntry>>> finalFuture = new AtomicReference<>(); |
||||
|
await(alias + SparkplugMessageType.NDATA.name()) |
||||
|
.atMost(40, TimeUnit.SECONDS) |
||||
|
.until(() -> { |
||||
|
finalFuture.set(tsService.findAllLatest(tenantId, savedGateway.getId())); |
||||
|
return finalFuture.get().get().size() == listTsKvEntry.size(); |
||||
|
}); |
||||
|
Assert.assertTrue("Expected tsKvEntrys is not equal Actual tsKvEntrys", listTsKvEntry.containsAll(finalFuture.get().get())); |
||||
|
Assert.assertTrue("Actual tsKvEntrys is not equal Expected tsKvEntrys", finalFuture.get().get().containsAll(listTsKvEntry)); |
||||
|
} |
||||
|
|
||||
|
protected void processClientWithCorrectAccessTokenPushNodeMetricBuildArraysSimple() throws Exception { |
||||
|
processClientWithCorrectNodeAccess(); |
||||
|
|
||||
|
String messageTypeName = SparkplugMessageType.NDATA.name(); |
||||
|
List<String> listKeys = new ArrayList<>(); |
||||
|
List<TsKvEntry> listTsKvEntry = new ArrayList<>(); |
||||
|
|
||||
|
SparkplugBProto.Payload.Builder ndataPayload = SparkplugBProto.Payload.newBuilder() |
||||
|
.setTimestamp(calendar.getTimeInMillis()) |
||||
|
.setSeq(getSeqNum()); |
||||
|
long ts = calendar.getTimeInMillis() - PUBLISH_TS_DELTA_MS; |
||||
|
|
||||
|
createdAddMetricValueArraysTsKv(listTsKvEntry, listKeys, ndataPayload, ts); |
||||
|
|
||||
|
if (client.isConnected()) { |
||||
|
client.publish(NAMESPACE + "/" + groupId + "/" + messageTypeName + "/" + edgeNode, |
||||
|
ndataPayload.build().toByteArray(), 0, false); |
||||
|
} |
||||
|
|
||||
|
AtomicReference<ListenableFuture<List<TsKvEntry>>> finalFuture = new AtomicReference<>(); |
||||
|
await(alias + SparkplugMessageType.NDATA.name()) |
||||
|
.atMost(40, TimeUnit.SECONDS) |
||||
|
.until(() -> { |
||||
|
finalFuture.set(tsService.findAllLatest(tenantId, savedGateway.getId())); |
||||
|
return finalFuture.get().get().size() == listTsKvEntry.size(); |
||||
|
}); |
||||
|
Assert.assertTrue("Expected tsKvEntrys is not equal Actual tsKvEntrys", listTsKvEntry.containsAll(finalFuture.get().get())); |
||||
|
Assert.assertTrue("Actual tsKvEntrys is not equal Expected tsKvEntrys", finalFuture.get().get().containsAll(listTsKvEntry)); |
||||
|
} |
||||
|
|
||||
|
private void createdAddMetricValuePrimitiveTsKv(List<TsKvEntry> listTsKvEntry, List<String> listKeys, |
||||
|
SparkplugBProto.Payload.Builder dataPayload, long ts) throws ThingsboardException { |
||||
|
|
||||
|
String keys = "MyInt8"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextInt8(), ts, Int8)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyInt16"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextInt16(), ts, Int16)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyInt32"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextInt32(), ts, Int32)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyInt64"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextInt64(), ts, Int64)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyUInt8"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextUInt8(), ts, UInt8)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyUInt16"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextUInt16(), ts, UInt16)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyUInt32I"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextUInt32I(), ts, UInt32)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyUInt32L"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextUInt32L(), ts, UInt32)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyUInt64"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextUInt64(), ts, UInt64)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyFloat"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvFloat(dataPayload, keys, nextFloat(0, 100), ts, MetricDataType.Float)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyDateTime"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvLong(dataPayload, keys, nextDateTime(), ts, MetricDataType.DateTime)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyDouble"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvDouble(dataPayload, keys, nextDouble(), ts, MetricDataType.Double)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyBoolean"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvBoolean(dataPayload, keys, nextBoolean(), ts, MetricDataType.Boolean)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyString"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvString(dataPayload, keys, nexString(), ts, MetricDataType.String)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyText"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvString(dataPayload, keys, nexString(), ts, MetricDataType.Text)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
keys = "MyUUID"; |
||||
|
listTsKvEntry.add(createdAddMetricTsKvString(dataPayload, keys, nexString(), ts, MetricDataType.UUID)); |
||||
|
listKeys.add(keys); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void createdAddMetricValueArraysTsKv(List<TsKvEntry> listTsKvEntry, List<String> listKeys, |
||||
|
SparkplugBProto.Payload.Builder dataPayload, long ts) throws ThingsboardException { |
||||
|
String keys = "MyBytesArray"; |
||||
|
byte[] bytes = {nextInt8(), nextInt8(), nextInt8()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, bytes, ts, Bytes, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyInt8Array"; |
||||
|
byte[] int8s = {nextInt8(), nextInt8(), nextInt8()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, int8s, ts, Int8Array, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyInt16Array"; |
||||
|
short[] int16s = {nextInt16(), nextInt16(), nextInt16()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, int16s, ts, Int16Array, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyInt32Array"; |
||||
|
int[] int32s = {nextInt32(), nextInt32(), nextInt32()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, int32s, ts, Int32Array, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyInt64Array"; |
||||
|
long[] int64s = {nextInt64(), nextInt64(), nextInt64()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, int64s, ts, Int64Array, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyUInt8Array"; |
||||
|
short[] uInt8s = {nextUInt16(), nextUInt16(), nextUInt16()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, uInt8s, ts, UInt8Array, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyUInt16Array"; |
||||
|
int[] uInt16s = {nextUInt16(), nextUInt16(), nextUInt16()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, uInt16s, ts, UInt16Array, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyUInt32LArray"; |
||||
|
long[] uInt32Ls = {nextUInt32L(), nextUInt32L(), nextUInt32L()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, uInt32Ls, ts, UInt32Array, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyUInt64Array"; |
||||
|
long[] uInt64s = {nextUInt64(), nextUInt64(), nextUInt64()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, uInt64s, ts, UInt64Array, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyFloatArray"; |
||||
|
float[] floats = {nextFloat(0,300), nextFloat(0,4000), nextFloat(10,10000)}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, floats, ts, FloatArray, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyDateTimeArray"; |
||||
|
long[] dateTimes = {nextDateTime(), nextDateTime(), nextDateTime()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, dateTimes, ts, DateTimeArray, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyDoubleArray"; |
||||
|
double [] doubles = {nextDouble(), nextDouble(), nextDouble()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, doubles, ts, DoubleArray, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyBooleanArray"; |
||||
|
boolean [] booleans = {nextBoolean(), nextBoolean(), nextBoolean()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, booleans, ts, BooleanArray, listTsKvEntry, listKeys); |
||||
|
|
||||
|
keys = "MyStringArray"; |
||||
|
String [] strings = {nexString(), nexString(), nexString()}; |
||||
|
createdAddMetricTsKvJson(dataPayload, keys, strings, ts, StringArray, listTsKvEntry, listKeys); |
||||
|
} |
||||
|
|
||||
|
private TsKvEntry createdAddMetricTsKvLong(SparkplugBProto.Payload.Builder dataPayload, String keys, Object value, |
||||
|
long ts, MetricDataType metricDataType) throws ThingsboardException { |
||||
|
TsKvEntry tsKvEntry = new BasicTsKvEntry(ts, new LongDataEntry(keys, Long.valueOf(String.valueOf(value)))); |
||||
|
|
||||
|
dataPayload.addMetrics(createMetric(value, tsKvEntry, metricDataType)); |
||||
|
return tsKvEntry; |
||||
|
} |
||||
|
|
||||
|
private TsKvEntry createdAddMetricTsKvFloat(SparkplugBProto.Payload.Builder dataPayload, String keys, Object value, |
||||
|
long ts, MetricDataType metricDataType) throws ThingsboardException { |
||||
|
var f = new BigDecimal(String.valueOf(value)); |
||||
|
TsKvEntry tsKvEntry = new BasicTsKvEntry(ts, new DoubleDataEntry(keys, f.doubleValue())); |
||||
|
dataPayload.addMetrics(createMetric(value, tsKvEntry, metricDataType)); |
||||
|
return tsKvEntry; |
||||
|
} |
||||
|
|
||||
|
private TsKvEntry createdAddMetricTsKvDouble(SparkplugBProto.Payload.Builder dataPayload, String keys, double value, |
||||
|
long ts, MetricDataType metricDataType) throws ThingsboardException { |
||||
|
var d = new BigDecimal(String.valueOf(value)); |
||||
|
TsKvEntry tsKvEntry = new BasicTsKvEntry(ts, new LongDataEntry(keys, d.longValueExact())); |
||||
|
dataPayload.addMetrics(createMetric(value, tsKvEntry, metricDataType)); |
||||
|
return tsKvEntry; |
||||
|
} |
||||
|
|
||||
|
private TsKvEntry createdAddMetricTsKvBoolean(SparkplugBProto.Payload.Builder dataPayload, String keys, boolean value, |
||||
|
long ts, MetricDataType metricDataType) throws ThingsboardException { |
||||
|
TsKvEntry tsKvEntry = new BasicTsKvEntry(ts, new BooleanDataEntry(keys, value)); |
||||
|
dataPayload.addMetrics(createMetric(value, tsKvEntry, metricDataType)); |
||||
|
return tsKvEntry; |
||||
|
} |
||||
|
|
||||
|
private TsKvEntry createdAddMetricTsKvString(SparkplugBProto.Payload.Builder dataPayload, String keys, String value, |
||||
|
long ts, MetricDataType metricDataType) throws ThingsboardException { |
||||
|
TsKvEntry tsKvEntry = new BasicTsKvEntry(ts, new StringDataEntry(keys, value)); |
||||
|
dataPayload.addMetrics(createMetric(value, tsKvEntry, metricDataType)); |
||||
|
return tsKvEntry; |
||||
|
} |
||||
|
|
||||
|
private void createdAddMetricTsKvJson(SparkplugBProto.Payload.Builder dataPayload, String keys, |
||||
|
Object values, long ts, MetricDataType metricDataType, |
||||
|
List<TsKvEntry> listTsKvEntry, |
||||
|
List<String> listKeys) throws ThingsboardException { |
||||
|
ArrayNode nodeArray = newArrayNode(); |
||||
|
switch (metricDataType) { |
||||
|
case Bytes: |
||||
|
case Int8Array: |
||||
|
for (byte b : (byte[])values) { |
||||
|
nodeArray.add(b); |
||||
|
} |
||||
|
break; |
||||
|
case Int16Array: |
||||
|
case UInt8Array: |
||||
|
for (short b : (short[])values) { |
||||
|
nodeArray.add(b); |
||||
|
} |
||||
|
break; |
||||
|
case Int32Array: |
||||
|
case UInt16Array: |
||||
|
case Int64Array: |
||||
|
case UInt32Array: |
||||
|
case UInt64Array: |
||||
|
case DateTimeArray: |
||||
|
if (values instanceof int[]) { |
||||
|
for (int b : (int[])values) { |
||||
|
nodeArray.add(b); |
||||
|
} |
||||
|
} else { |
||||
|
for (long b : (long[])values) { |
||||
|
nodeArray.add(b); |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
case DoubleArray: |
||||
|
for (double b : (double[])values) { |
||||
|
nodeArray.add(b); |
||||
|
} |
||||
|
break; |
||||
|
case FloatArray: |
||||
|
for (float b : (float[])values) { |
||||
|
nodeArray.add(b); |
||||
|
} |
||||
|
break; |
||||
|
case BooleanArray: |
||||
|
for (boolean b : (boolean[])values) { |
||||
|
nodeArray.add(b); |
||||
|
} |
||||
|
break; |
||||
|
case StringArray: |
||||
|
for (String b : (String[])values) { |
||||
|
nodeArray.add(b); |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
throw new IllegalStateException("Unexpected value: " + metricDataType); |
||||
|
} |
||||
|
if (nodeArray.size() > 0) { |
||||
|
Optional<TsKvEntry> tsKvEntryOptional = Optional.of(new BasicTsKvEntry(ts, new JsonDataEntry(keys, nodeArray.toString()))); |
||||
|
if (tsKvEntryOptional.isPresent()) { |
||||
|
dataPayload.addMetrics(createMetric(values, tsKvEntryOptional.get(), metricDataType)); |
||||
|
listTsKvEntry.add(tsKvEntryOptional.get()); |
||||
|
listKeys.add(keys); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private byte nextInt8() { |
||||
|
return (byte) random.nextInt(Byte.MIN_VALUE, Byte.MAX_VALUE); |
||||
|
} |
||||
|
|
||||
|
private short nextUInt8() { |
||||
|
return (short) random.nextInt(0, Byte.MAX_VALUE * 2 + 1); |
||||
|
} |
||||
|
|
||||
|
private short nextInt16() { |
||||
|
return (short) random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE); |
||||
|
} |
||||
|
|
||||
|
private short nextUInt16() { |
||||
|
return (short) random.nextInt(0, Short.MAX_VALUE * 2 + 1); |
||||
|
} |
||||
|
|
||||
|
private int nextInt32() { |
||||
|
return random.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE); |
||||
|
} |
||||
|
|
||||
|
private int nextUInt32I() { |
||||
|
return random.nextInt(0, Integer.MAX_VALUE); |
||||
|
} |
||||
|
|
||||
|
private long nextUInt32L() { |
||||
|
long l = Integer.MAX_VALUE; |
||||
|
return random.nextLong(0, l * 2 + 1); |
||||
|
} |
||||
|
|
||||
|
private long nextInt64() { |
||||
|
return random.nextLong(Long.MIN_VALUE, Long.MAX_VALUE); |
||||
|
} |
||||
|
|
||||
|
private long nextUInt64() { |
||||
|
double d = Long.MAX_VALUE; |
||||
|
return random.nextLong(0, (long) (d * 2 + 1)); |
||||
|
} |
||||
|
|
||||
|
private double nextDouble() { |
||||
|
return random.nextDouble(Long.MIN_VALUE, Long.MAX_VALUE); |
||||
|
} |
||||
|
|
||||
|
private long nextDateTime() { |
||||
|
long min = calendar.getTimeInMillis() - PUBLISH_TS_DELTA_MS; |
||||
|
long max = calendar.getTimeInMillis(); |
||||
|
return random.nextLong(min, max); |
||||
|
} |
||||
|
|
||||
|
private float nextFloat(float min, float max) { |
||||
|
if (min >= max) |
||||
|
throw new IllegalArgumentException("max must be greater than min"); |
||||
|
float result = ThreadLocalRandom.current().nextFloat() * (max - min) + min; |
||||
|
if (result >= max) // correct for rounding
|
||||
|
result = Float.intBitsToFloat(Float.floatToIntBits(max) - 1); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private boolean nextBoolean() { |
||||
|
return random.nextBoolean(); |
||||
|
} |
||||
|
|
||||
|
private String nexString() { |
||||
|
return java.util.UUID.randomUUID().toString(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
/** |
||||
|
* 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.sparkplug.timeseries; |
||||
|
|
||||
|
import org.eclipse.paho.mqttv5.common.MqttException; |
||||
|
import org.junit.After; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
/** |
||||
|
* Created by nickAS21 on 12.01.23 |
||||
|
*/ |
||||
|
@DaoSqlTest |
||||
|
public class MqttV5ClientSparkplugBTelemetryTest extends AbstractMqttV5ClientSparkplugTelemetryTest { |
||||
|
|
||||
|
@Before |
||||
|
public void beforeTest() throws Exception { |
||||
|
beforeSparkplugTest(); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void afterTest () throws MqttException { |
||||
|
if (client.isConnected()) { |
||||
|
client.disconnect(); } |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testClientWithCorrectAccessTokenPublishNBIRTH() throws Exception { |
||||
|
processClientWithCorrectAccessTokenPublishNBIRTH(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testClientWithCorrectAccessTokenPublishNCMDReBirth() throws Exception { |
||||
|
processClientWithCorrectAccessTokenPublishNCMDReBirth(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testClientWithCorrectAccessTokenPushNodeMetricBuildPrimitiveSimple() throws Exception { |
||||
|
processClientWithCorrectAccessTokenPushNodeMetricBuildPrimitiveSimple(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testClientWithCorrectAccessTokenPushNodeMetricBuildPArraysSimple() throws Exception { |
||||
|
processClientWithCorrectAccessTokenPushNodeMetricBuildArraysSimple(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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,296 @@ |
|||||
|
/** |
||||
|
* 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.node.ArrayNode; |
||||
|
import com.fasterxml.jackson.databind.ser.std.FileSerializer; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
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.io.ByteArrayInputStream; |
||||
|
import java.io.ObjectInputStream; |
||||
|
import java.math.BigDecimal; |
||||
|
import java.nio.ByteBuffer; |
||||
|
import java.nio.DoubleBuffer; |
||||
|
import java.nio.FloatBuffer; |
||||
|
import java.nio.IntBuffer; |
||||
|
import java.nio.LongBuffer; |
||||
|
import java.nio.ShortBuffer; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
import static org.thingsboard.common.util.JacksonUtil.newArrayNode; |
||||
|
|
||||
|
/** |
||||
|
* 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 Optional.empty(); |
||||
|
} |
||||
|
// Otherwise convert the value based on the type
|
||||
|
int metricType = protoMetric.getDatatype(); |
||||
|
TransportProtos.KeyValueProto.Builder builderProto = TransportProtos.KeyValueProto.newBuilder(); |
||||
|
ArrayNode nodeArray = newArrayNode(); |
||||
|
MetricDataType metricDataType = MetricDataType.fromInteger(metricType); |
||||
|
try { |
||||
|
switch (metricDataType) { |
||||
|
case Boolean: |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.BOOLEAN_V) |
||||
|
.setBoolV(protoMetric.getBooleanValue()).build()); |
||||
|
case DateTime: |
||||
|
case Int64: |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.LONG_V) |
||||
|
.setLongV(protoMetric.getLongValue()).build()); |
||||
|
case Float: |
||||
|
var f = new BigDecimal(String.valueOf(protoMetric.getFloatValue())); |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.DOUBLE_V) |
||||
|
.setDoubleV(f.doubleValue()).build()); |
||||
|
case Double: |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.DOUBLE_V) |
||||
|
.setDoubleV(protoMetric.getDoubleValue()).build()); |
||||
|
case Int8: |
||||
|
case UInt8: |
||||
|
case Int16: |
||||
|
case Int32: |
||||
|
case UInt16: |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.LONG_V) |
||||
|
.setLongV(protoMetric.getIntValue()).build()); |
||||
|
case UInt32: |
||||
|
case UInt64: |
||||
|
if (protoMetric.hasIntValue()) { |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.LONG_V) |
||||
|
.setLongV(protoMetric.getIntValue()).build()); |
||||
|
} else if (protoMetric.hasLongValue()) { |
||||
|
return Optional.of(builderProto.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 " + MetricDataType.fromInteger(metricType).name() + " datatype " + metricType, ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
case String: |
||||
|
case Text: |
||||
|
case UUID: |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV(protoMetric.getStringValue()).build()); |
||||
|
// byte[]
|
||||
|
case BooleanArray: |
||||
|
ByteBuffer booleanByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()); |
||||
|
while (booleanByteBuffer.hasRemaining()){ |
||||
|
nodeArray.add(booleanByteBuffer.get() == (byte) 0 ? false : true); |
||||
|
} |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setJsonV(nodeArray.toString()).build()); |
||||
|
// byte[]
|
||||
|
case Bytes: |
||||
|
case Int8Array: |
||||
|
ByteBuffer byteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()); |
||||
|
while (byteBuffer.hasRemaining()){ |
||||
|
nodeArray.add(byteBuffer.get()); |
||||
|
} |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setJsonV(nodeArray.toString()).build()); |
||||
|
// short[]
|
||||
|
case Int16Array: |
||||
|
case UInt8Array: |
||||
|
ShortBuffer shortByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()) |
||||
|
.asShortBuffer(); |
||||
|
while (shortByteBuffer.hasRemaining()){ |
||||
|
nodeArray.add(shortByteBuffer.get()); |
||||
|
} |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setJsonV(nodeArray.toString()).build()); |
||||
|
// int[]
|
||||
|
case Int32Array: |
||||
|
case UInt16Array: |
||||
|
IntBuffer intByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()) |
||||
|
.asIntBuffer(); |
||||
|
while (intByteBuffer.hasRemaining()){ |
||||
|
nodeArray.add(intByteBuffer.get()); |
||||
|
} |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setJsonV(nodeArray.toString()).build()); |
||||
|
// float[]
|
||||
|
case FloatArray: |
||||
|
FloatBuffer floatByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()) |
||||
|
.asFloatBuffer(); |
||||
|
while (floatByteBuffer.hasRemaining()){ |
||||
|
nodeArray.add(floatByteBuffer.get()); |
||||
|
} |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setJsonV(nodeArray.toString()).build()); |
||||
|
// double[]
|
||||
|
case DoubleArray: |
||||
|
DoubleBuffer doubleByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()) |
||||
|
.asDoubleBuffer(); |
||||
|
while (doubleByteBuffer.hasRemaining()){ |
||||
|
nodeArray.add(doubleByteBuffer.get()); |
||||
|
} |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setJsonV(nodeArray.toString()).build()); |
||||
|
// long[]
|
||||
|
case DateTimeArray: |
||||
|
case Int64Array: |
||||
|
case UInt64Array: |
||||
|
case UInt32Array: |
||||
|
LongBuffer longByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()) |
||||
|
.asLongBuffer(); |
||||
|
while (longByteBuffer.hasRemaining()){ |
||||
|
nodeArray.add(longByteBuffer.get()); |
||||
|
} |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setJsonV(nodeArray.toString()).build()); |
||||
|
case StringArray: |
||||
|
ByteBuffer stringByteBuffer = ByteBuffer.wrap(protoMetric.getBytesValue().toByteArray()); |
||||
|
final ByteArrayInputStream byteArrayInputStream = |
||||
|
new ByteArrayInputStream(stringByteBuffer.array()); |
||||
|
final ObjectInputStream objectInputStream = |
||||
|
new ObjectInputStream(byteArrayInputStream); |
||||
|
final String[] stringArray = (String[]) objectInputStream.readObject(); |
||||
|
objectInputStream.close(); |
||||
|
for (String s: stringArray) { |
||||
|
nodeArray.add(s); |
||||
|
} |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.JSON_V) |
||||
|
.setJsonV(nodeArray.toString()).build()); |
||||
|
case DataSet: |
||||
|
case Template: |
||||
|
case File: |
||||
|
//TODO
|
||||
|
// Build the and create the DataSet
|
||||
|
/** |
||||
|
SparkplugBProto.Payload.DataSet protoDataSet = protoMetric.getDatasetValue(); |
||||
|
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(builderProto.setKey(key).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV(protoDataSet.toString()).build()); |
||||
|
**/ |
||||
|
//TODO
|
||||
|
// Build the and create the Template
|
||||
|
/** |
||||
|
SparkplugBProto.Payload.Template protoTemplate = protoMetric.getTemplateValue(); |
||||
|
return Optional.of(builderProto.setKey(key).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV( protoTemplate.toString()).build()); |
||||
|
**/ |
||||
|
//TODO
|
||||
|
// Build the and create the File
|
||||
|
/** |
||||
|
String filename = protoMetric.getMetadata().getFileName(); |
||||
|
return Optional.of(builderPrbyteValueoto.setKey(key + "_" + filename).setType(TransportProtos.KeyValueType.STRING_V) |
||||
|
.setStringV(Hex.encodeHexString((protoMetric.getBytesValue().toByteArray()))).build()); |
||||
|
**/ |
||||
|
return Optional.empty(); |
||||
|
case Unknown: |
||||
|
default: |
||||
|
throw new ThingsboardException("Failed to decode: Unknown MetricDataType " + metricType, ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
} catch (Exception e){ |
||||
|
log.error("", e); |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@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