From e5c6be6e08e41ecb9ddcc2b538e47c709e5ad38e Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 17 Oct 2024 19:10:50 +0300 Subject: [PATCH] lwm2m: add tests --- .../client/LwM2mBinaryAppDataContainer.java | 2 +- .../rpc/sql/RpcLwm2mIntegrationWriteTest.java | 184 ++++++++++++++++-- .../DefaultLwM2mDownlinkMsgHandler.java | 10 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 23 +-- .../lwm2m/utils/LwM2MTransportUtil.java | 51 ++++- .../lwm2m/utils/LwM2mValueConverterImpl.java | 17 +- .../common/transport/util/JsonUtils.java | 29 ++- 7 files changed, 267 insertions(+), 49 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java index 3efd6365dd..9c5b63bc3d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -129,7 +129,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements fireResourceChange(resourceId); return WriteResponse.success(); } else { - WriteResponse.badRequest("Invalidate value ..."); + return WriteResponse.badRequest("Invalidate value ..."); } case 1: setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue())); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java index e706b72eac..dce5233984 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationWriteTest.java @@ -20,6 +20,7 @@ import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import static org.junit.Assert.assertEquals; @@ -76,7 +77,157 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue + ", type=STRING]"; assertTrue(actualValues.contains(expected)); } - + @Test + public void testWriteReplaceValueMultipleResource_Result_CHANGED_Multi_Instance_Resource_must_One() throws Exception { + int resourceInstanceId0 = 0; + String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0; + // base64/String + String expectedValue = "QUJDREVGRw"; + String actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + String actualValues = rpcActualResult.get("value").asText(); + byte[] expectedValue0 = TbUtils.base64ToBytes(expectedValue); + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // base64/String + expectedValue = "ABCDEFG"; + actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expectedValue0 = TbUtils.base64ToBytes(expectedValue); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // hexDecimal/String + expectedValue = "01ABCDEF"; + actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue.length()/2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // Integer + Integer expectedIntegerValue = 1234566; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedIntegerValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedIntegerValue = Integer.MAX_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedIntegerValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedIntegerValue = Integer.MIN_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedIntegerValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // Long + Long expectedLongValue = 4406483977L; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedLongValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedLongValue = Long.MAX_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedLongValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedLongValue = Long.MIN_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedLongValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // Float to byte[]: byte[] bytes = ByteBuffer.allocate(4).putFloat(((Float) value).floatValue()).array(); + // Float from byte[]: float f = ByteBuffer.wrap(bytes).getFloat(); + Float expectedFloatValue = 8.02f; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedFloatValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedFloatValue = Float.MAX_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedFloatValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedFloatValue = Float.MIN_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedFloatValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + // Double to byte[]: byte[] bytes = ByteBuffer.allocate(8).putDouble(((Double) value).doubleValue()).array(); + // Double from byte[]: double d = ByteBuffer.wrap(bytes).getDouble(); + Double expectedDoubleValue = 1022.5906d; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedDoubleValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 4 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedDoubleValue = Double.MAX_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedDoubleValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + expectedDoubleValue = Double.MIN_VALUE; + actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedDoubleValue); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); + actualResult = sendRPCReadById(expectedPath); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + actualValues = rpcActualResult.get("value").asText(); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + 8 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + } /** * id @@ -88,9 +239,9 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; int resourceInstanceId0 = 0; int resourceInstanceId15 = 15; - String expectedValue0 = "0000ad45675600"; - String expectedValue15 = "1525ad45675600cdef"; - String expectedValue = "{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId15 + "\":\"" + expectedValue15 + "\"}"; + String expectedValue0 = "1525ad45675600cdef"; + Integer expectedValue15 = Integer.MAX_VALUE; + String expectedValue = "{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId15 + "\":" + expectedValue15 + "}"; String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); @@ -99,12 +250,12 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadById(expectedPath15); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); - expected = "LwM2mResourceInstance [id=" + resourceInstanceId15 + ", value=" + expectedValue15.length()/2 + "Bytes, type=OPAQUE]"; + expected = "LwM2mResourceInstance [id=" + resourceInstanceId15 + ", value=" + Integer.toHexString(expectedValue15).length()/2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); } @@ -130,7 +281,7 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertFalse(actualValues.contains(expected)); } @@ -193,16 +344,16 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); String expectedPath0 = expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0; - String expectedPath25 =expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25; + String expectedPath25 = expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25; actualResult = sendRPCReadById(expectedPath0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadById(expectedPath25); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); - expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); } @@ -232,13 +383,14 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); - expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; - assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadByKey(expectedKey3_0_14); + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length() / 2 + "Bytes, type=OPAQUE]"; + assertTrue(actualValues.contains(expected)); + actualResult = sendRPCReadByKey(expectedKey3_0_14); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]"; @@ -270,12 +422,12 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadById(expectedPath_19_0 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); actualValues = rpcActualResult.get("value").asText(); - expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; + expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); } @@ -301,7 +453,7 @@ public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTes actualResult = sendRPCReadById(expectedPath19_1_0_2); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualValues = rpcActualResult.get("value").asText(); - String expected = "LwM2mResourceInstance [id=" + RESOURCE_INSTANCE_ID_2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]"; + String expected = "LwM2mResourceInstance [id=" + RESOURCE_INSTANCE_ID_2 + ", value=" + expectedValue19_1_0_2.length() / 2 + "Bytes, type=OPAQUE]"; assertTrue(actualValues.contains(expected)); actualResult = sendRPCReadByKey(expectedKey3_0_14); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 80221eb5ea..bc617fcf57 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import com.google.gson.JsonParser; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; @@ -396,7 +397,12 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im String msgError = ""; if (resourceModelWrite.multiple) { try { - Map value = convertMultiResourceValuesFromRpcBody(request.getValue(), resourceModelWrite.type, request.getObjectId()); + Object valueForMultiResource = request.getValue(); + if (resultIds.isResourceInstance()) { + String resourceInstance = "{" + resultIds.getResourceInstanceId() + "=" + request.getValue() + "}"; + valueForMultiResource = JsonParser.parseString(resourceInstance); + } + Map value = convertMultiResourceValuesFromRpcBody(valueForMultiResource, resourceModelWrite.type, request.getObjectId()); downlink = new WriteRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), value, resourceModelWrite.type); } catch (Exception e) { @@ -707,7 +713,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(versionedId)); if (pathIds.isResourceInstance() || pathIds.isResource()) { ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); - if (resourceModel != null && (pathIds.isResourceInstance() || (pathIds.isResource() && !resourceModel.multiple))) { + if (resourceModel != null && !resourceModel.multiple) { ContentFormat[] desiredFormats; if (OBJLNK.equals(resourceModel.type)) { desiredFormats = new ContentFormat[]{ContentFormat.LINK, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 4655a27316..ca2a82968b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -277,17 +277,6 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class); - LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(versionedId)); - if (path.isResource()) { - ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); - if (resourceModel != null && resourceModel.multiple) { - try { - Map value = convertMultiResourceValuesFromRpcBody(requestBody.getValue(), resourceModel.type, versionedId); - requestBody.setValue(value); - } catch (Exception e) { - } - } - } TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) .value(requestBody.getValue()) .timeout(clientContext.getRequestTimeout(client)).build(); @@ -330,7 +319,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { if (versionedId == null) { if (path.isResourceInstance()) { - setValueToCompositeNodes(client, newNodes, nodes, key, value.toString()); + setValueToCompositeNodes(client, newNodes, nodes, key, value); } else if (path.isResource()) { validateResource(client, newNodes, nodes, key , value); } else if (path.isObjectInstance() && value instanceof Map) { @@ -342,7 +331,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { "The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", nodes)); } } else { - setValueToCompositeNodes(client, newNodes, nodes, versionedId, value.toString()); + setValueToCompositeNodes(client, newNodes, nodes, versionedId, value); } }); return newNodes; @@ -351,10 +340,10 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void validateResource(LwM2mClient client, Map newNodes, Map nodes, String resourceId , Object value) { if (value instanceof Map) { ((Map) value).forEach((k, v) -> { - setValueToCompositeNodes(client, newNodes, nodes, validateResourceId (resourceId, k.toString(), nodes), v.toString()); + setValueToCompositeNodes(client, newNodes, nodes, validateResourceId (resourceId, k.toString(), nodes), v); }); } else { - setValueToCompositeNodes(client, newNodes, nodes, resourceId, value.toString()); + setValueToCompositeNodes(client, newNodes, nodes, resourceId, value); } } @@ -368,7 +357,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } } - private void setValueToCompositeNodes (LwM2mClient client, Map newNodes, Map nodes, String versionedId , String value) { + private void setValueToCompositeNodes (LwM2mClient client, Map newNodes, Map nodes, String versionedId , Object value) { // validate value. Must be only primitive, not JsonObject or JsonArray try { JsonElement element = JsonUtils.parse(value); @@ -378,7 +367,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } // convert value from JsonPrimitive() to resource/ResourceInstance type ResourceModel resourceModel = client.getResourceModel(versionedId, modelProvider); - Object newValue = convertValueByTypeResource(value, resourceModel.type, versionedId); + Object newValue = convertValueByTypeResource(element, resourceModel.type, versionedId); // add new value after convert newNodes.put(fromVersionedIdToObjectId(versionedId), newValue); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index 6b90ae21b6..66f68f0a05 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.utils; import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.core.model.LwM2mModel; @@ -27,7 +28,6 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.util.Hex; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; @@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.ota.OtaPackageKey; -import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion; import org.thingsboard.server.transport.lwm2m.server.LwM2mOtaConvert; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @@ -62,6 +61,7 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING; import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; +import static org.thingsboard.server.common.transport.util.JsonUtils.convertToJsonObject; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; @@ -180,9 +180,11 @@ public class LwM2MTransportUtil { */ public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) { switch (value.getClass().getSimpleName()) { + case "Float": case "Double": return FLOAT; case "Integer": + case "Long": return INTEGER; case "String": return STRING; @@ -199,6 +201,30 @@ public class LwM2MTransportUtil { } } + public static Object getJsonPrimitiveValue(JsonPrimitive value) { + if(value.isString()) { + return value.getAsString(); + } else if (value.isNumber()){ + try { + return Integer.valueOf(value.toString()); + } catch (NumberFormatException i) { + try { + return Long.valueOf(value.toString()); + } catch (NumberFormatException l){ + if (value.getAsFloat() >= Float.MIN_VALUE && value.getAsFloat() <= Float.MAX_VALUE) { + return value.getAsFloat(); + } else { + return value.getAsDouble(); + } + } + } + } else if (value.isBoolean()){ + return value.getAsBoolean(); + } else { + return null; + } + } + public static void validateVersionedId(LwM2mClient client, HasVersionedId request) { String msgExceptionStr = ""; if (request.getObjectId() == null) { @@ -212,22 +238,29 @@ public class LwM2MTransportUtil { } public static Map convertMultiResourceValuesFromRpcBody(Object value, ResourceModel.Type type, String versionedId) throws Exception { - String valueJsonStr = JacksonUtil.toString(value); - JsonElement element = JsonUtils.parse(valueJsonStr); - return convertMultiResourceValuesFromJson(element, type, versionedId); + if (value instanceof JsonElement) { + return convertMultiResourceValuesFromJson((JsonElement) value, type, versionedId); + } else if (value instanceof Map) { + JsonElement valueConvert = convertToJsonObject((Map) value); + return convertMultiResourceValuesFromJson(valueConvert, type, versionedId); + } else { + return null; + } } public static Map convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) { Map newValues = new HashMap<>(); newValProto.getAsJsonObject().entrySet().forEach((obj) -> { - newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource(obj.getValue().getAsString(), type, versionedId)); + Object valueByTypeResource = convertValueByTypeResource(obj.getValue(), type, versionedId); + newValues.put(Integer.valueOf(obj.getKey()), valueByTypeResource); }); return newValues; } - public static Object convertValueByTypeResource(String value, ResourceModel.Type type, String versionedId) { - return LwM2mValueConverterImpl.getInstance().convertValue(value, - STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); + public static Object convertValueByTypeResource(Object value, ResourceModel.Type type, String versionedId) { + Object valueCurrent = getJsonPrimitiveValue((JsonPrimitive) value); + return LwM2mValueConverterImpl.getInstance().convertValue(valueCurrent, + equalsResourceTypeGetSimpleName(valueCurrent), type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); } /** diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index 3125ae63f5..a584211605 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -25,6 +25,7 @@ import org.eclipse.leshan.core.util.Hex; import org.thingsboard.server.common.data.StringUtils; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Base64; @@ -165,7 +166,19 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { } break; case OPAQUE: - if (currentType == Type.STRING) { + if (currentType == Type.INTEGER) { + if (value instanceof Integer) { + return ByteBuffer.allocate(4).putInt((Integer) value).array(); + } else { + return ByteBuffer.allocate(8).putLong((Long) value).array(); + } + } else if (currentType == Type.FLOAT) { + if (value instanceof Float) { + return ByteBuffer.allocate(4).putFloat((Float) value).array(); + } else { + return ByteBuffer.allocate(8).putDouble((Double) value).array(); + } + } else if (currentType == Type.STRING) { /** let's assume we received an hexadecimal string */ log.debug("Trying to convert hexadecimal/base64 string [{}] to byte array", value); try { @@ -178,6 +191,8 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { value, resourcePath); } } + } else if (currentType == Type.BOOLEAN) { + return new byte[] {(byte)((boolean)value ? 1 : 0)}; } break; case OBJLNK: diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java index 1f273e6f04..ed0b425c76 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java @@ -18,9 +18,11 @@ package org.thingsboard.server.common.transport.util; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import java.util.List; +import java.util.Map; public class JsonUtils { @@ -47,9 +49,30 @@ public class JsonUtils { } return json; } - - public static JsonElement parse(String params) { - return JsonParser.parseString(params); + public static JsonElement parse(Object value) { + if (value instanceof Integer) { + return new JsonPrimitive((Integer) value); + } else if (value instanceof Long) { + return new JsonPrimitive((Long) value); + } else if (value instanceof String) { + return JsonParser.parseString((String) value); + } else if (value instanceof Boolean) { + return new JsonPrimitive((Boolean) value); + } else if (value instanceof Double) { + return new JsonPrimitive((Double) value); + } else if (value instanceof Float) { + return new JsonPrimitive((Float) value); + } else { + throw new IllegalArgumentException("Unsupported type: " + value.getClass().getSimpleName()); + } } + public static JsonObject convertToJsonObject(Map map) { + JsonObject jsonObject = new JsonObject(); + for (Map.Entry entry : map.entrySet()) { + jsonObject.add(entry.getKey(), parse(entry.getValue())); + } + + return jsonObject; + } }