diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index b5f23c3e9a..1c5cf06c15 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -49,6 +49,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @DaoSqlTest public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -74,7 +75,11 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg protected String objectIdVer_3303; protected static AtomicInteger endpointSequence = new AtomicInteger(); protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc"; + + protected String idVer_3_0_0; protected String idVer_3_0_9; + protected String id_3_0_9; + protected String idVer_19_0_0; public AbstractRpcLwM2MIntegrationTest() { @@ -125,7 +130,9 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + FIRMWARE)).findFirst().get(); objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + SOFTWARE_MANAGEMENT)).findFirst().get(); + idVer_3_0_0 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; idVer_3_0_9 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9; + id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC = diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java index 94ee1eb48e..846d4091c0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java @@ -16,14 +16,22 @@ package org.thingsboard.server.transport.lwm2m.rpc.sql; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.server.registration.Registration; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; @@ -42,8 +50,12 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; +@Slf4j public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationObserveTest { + @SpyBean + DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest; + /** * ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0/0"]} - Ok @@ -53,11 +65,11 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mResourceInstance() throws Exception { sendCancelObserveAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValues = rpcActualResult.get("value").asText(); @@ -78,10 +90,10 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIds = "[\"" + expectedIdVer19_1_0 + "\", \"" + expectedIdVer5_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actual= rpcActualResult.get("value").asText(); + String actual = rpcActualResult.get("value").asText(); assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource")); assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer5_0) + "=LwM2mObjectInstance")); } @@ -97,7 +109,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValues = rpcActualResult.get("value").asText(); @@ -117,10 +129,10 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIds = "[\"" + expectedIdVer5_0 + "\", \"" + expectedIdVer5_0_2 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String actual= rpcActualResult.get("error").asText(); + String actual = rpcActualResult.get("error").asText(); String expected = "Invalid path list : /5/0 and /5/0/2 are overlapped paths"; assertTrue(expected.equals(actual)); } @@ -133,10 +145,10 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt @Test public void testObserveCompositeThereAreObservationOneResource_Result_CONTENT_Value_ObservationAddIfAbsent() throws Exception { String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String expectedResult = "/3/0/9=LwM2mSingleResource [id=9"; @@ -152,11 +164,11 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt sendCancelObserveAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValues = rpcActualResult.get("value").asText(); @@ -232,7 +244,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); + String actualValues = rpcActualResultReadAll.get("value").asText(); String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); @@ -249,7 +261,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt @Test public void testObserveReadAll_Result_CONTENT_Value_SingleObservation_Only() throws Exception { sendCancelObserveAllWithAwait(deviceId); - + String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; String actualResult3_0_9 = sendObserve("Observe", idVer_3_0_9); @@ -267,7 +279,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); + String actualValues = rpcActualResultReadAll.get("value").asText(); assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_14))); assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0_0))); @@ -284,11 +296,11 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt sendCancelObserveAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_2= objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; + String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValues = rpcActualResult.get("value").asText(); @@ -298,10 +310,9 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - actualValues = rpcActualResultReadAll.get("value").asText(); + actualValues = rpcActualResultReadAll.get("value").asText(); assertFalse(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_2))); - } /** @@ -312,17 +323,17 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt @Test public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_5() throws Exception { sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite + // ObserveComposite String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // ObserveCompositeCancel - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + // ObserveCompositeCancel + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals("5", rpcActualResult.get("value").asText()); @@ -338,15 +349,15 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt @Test public void testObserveCompositeOneObjectAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_3() throws Exception { sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite + // ObserveComposite String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // ObserveCompositeCancel - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + // ObserveCompositeCancel + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals("3", rpcActualResult.get("value").asText()); @@ -363,21 +374,21 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt @Test public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_OneObjectAnyResource_Result_Content_Count_4() throws Exception { sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite + // ObserveComposite String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; - String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); awaitObserveReadAll(5, deviceId); - // ObserveCompositeCancel + // ObserveCompositeCancel expectedIds = "[\"" + objectInstanceIdVer_5 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals("4", rpcActualResult.get("value").asText()); // CNT = 4 ("/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"9) @@ -385,7 +396,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); + String actualValues = rpcActualResultReadAll.get("value").asText(); assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_9) + "\"]", actualValues); } @@ -397,26 +408,26 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt @Test public void testObserveOneObjectAnyResources_Result_CONTENT_Cancel_OneResourceFromObjectAnyResource_Result_BAD_REQUEST_Cancel_OneObject_Result_CONTENT() throws Exception { sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite + // ObserveComposite sendCancelObserveAllWithAwait(deviceId); String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; - String expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // ObserveCompositeCancel + // ObserveCompositeCancel expectedIds = "[\"" + expectedIdVer19_1_0_0 + "\", \"" + idVer_3_0_9 + "\"]"; - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + fromVersionedIdToObjectId(idVer_3_0_9); assertTrue(rpcActualResult.get("error").asText().contains(expectedValue)); - // ObserveCompositeCancel + // ObserveCompositeCancel expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals("2", rpcActualResult.get("value").asText()); @@ -424,12 +435,12 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); + String actualValues = rpcActualResultReadAll.get("value").asText(); assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer5_0_3) + "\"]", actualValues); } - /** + /** * ObserveComposite {"ids":["/3/0/9", "/3/0/14", "/5/0/3", "/3/0/15", "/19/1/0/0"]} - Ok * ObserveCancel {"id":"/3/0/9"} -> INTERNAL_SERVER_ERROR * ObserveCompositeCancel {"ids":["/3/0/9", "/19/1/0/0", "/3]} - Ok @@ -439,19 +450,19 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt @Test public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_OneResource_OneObjectAnyResource_Result_Content_Count_4() throws Exception { sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite + // ObserveComposite sendCancelObserveAllWithAwait(deviceId); String expectedIdVer3_0_14 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14; - String expectedIdVer3_0_15= objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_15; + String expectedIdVer3_0_15 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_15; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; - String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer3_0_14 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_15 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer3_0_14 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_15 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // ObserveCompositeCancel + // ObserveCompositeCancel expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\", \"" + objectIdVer_3 + "\"]"; - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals("4", rpcActualResult.get("value").asText()); @@ -459,17 +470,44 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); + String actualValues = rpcActualResultReadAll.get("value").asText(); assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer5_0_3) + "\"]", actualValues); } + /** + * ObserveCancelAll + * Observe {"id":"/3/0/9"} + * updateRegistration + * idResources_/3/0/9 => updateAttrTelemetry >= 10 times + */ + @Test + public void testObserveCompositeResource_Update_AfterUpdateRegistration() throws Exception { + sendCancelObserveAllWithAwait(deviceId); + + int cntUpdate = 3; + verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) + .updatedReg(Mockito.any(Registration.class)); + + log.warn("After ObserveReadAll after cancel - send composite observe /3303_1.0/0/5700"); + + String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; + String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; + String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; + String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0; + String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\", \"" + expectedKey3_0_9 + "\"]"; + String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys); + + + cntUpdate = 20; + verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) + .updateAttrTelemetry(Mockito.any(Registration.class), argThat(arg -> arg.equals(idVer_3_0_9) || arg.equals(idVer_19_0_0))); + } private String sendObserve(String method, String params) throws Exception { String sendRpcRequest; if (params == null) { sendRpcRequest = "{\"method\": \"" + method + "\"}"; - } - else { + } else { sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; } return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); @@ -481,7 +519,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt } private String sendCompositeRPCByKeys(String method, String keys) throws Exception { - String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + String sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java index e1717c9b61..9518010f6a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java @@ -23,6 +23,8 @@ import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import java.util.Arrays; @@ -33,9 +35,10 @@ import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +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.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; -import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVerId; public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest { @@ -172,4 +175,19 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } + + private String convertObjectIdToVerId(String path, String ver) { + ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); + try { + String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); + if (keyArray.length > 1) { + keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver; + return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); + } else { + return path; + } + } catch (Exception e) { + return null; + } + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index 5a849e70e4..8fdf1994b6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -52,10 +52,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveReadAll_Count_2_CancelAll_Count_0_Ok() throws Exception { - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); + String actualValuesReadAll = sendRpcObserveWithResultValue("ObserveReadAll", null); assertEquals(2, actualValuesReadAll.split(",").length); String expected = "\"SingleObservation:/19/0/0\""; assertTrue(actualValuesReadAll.contains(expected)); @@ -70,15 +67,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { sendCancelObserveAllWithAwait(deviceId); - String idVer_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - String actualResult = sendRpcObserve("Observe", idVer_3_0_9); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); - assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); int cntUpdate = 3; - verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) + verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) .onUpdateValueAfterReadResponse(Mockito.any(Registration.class), eq(idVer_3_0_9), Mockito.any(ReadResponse.class)); } @@ -90,13 +82,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO public void testObserveOneObjectInstance_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { sendCancelObserveAllWithAwait(deviceId); String idVer_3_0 = objectInstanceIdVer_3; - String actualResult = sendRpcObserve("Observe", idVer_3_0); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); - assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0); + int cntUpdate = 3; - verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) + verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9)); } @@ -108,17 +97,13 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO public void testObserveOneObject_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { sendCancelObserveAllWithAwait(deviceId); String idVer_3_0 = objectInstanceIdVer_3; - String actualResult = sendRpcObserve("Observe", idVer_3_0); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); - assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0); + int cntUpdate = 3; - verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) + verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9)); } - /** * Repeated request on Observe * Observe {"id":"/3_1.2/0/0"} @@ -126,15 +111,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveRepeated_Result_CONTENT_AddIfAbsent() throws Exception { - String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - String actualResult = sendRpcObserve("Observe", idVer_3_0_0); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - actualResult = sendRpcObserve("Observe", idVer_3_0_0); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + sendRpcObserveWithResultValue("Observe", idVer_3_0_0); + String rpcActualResult = sendRpcObserveWithResultValue("Observe", idVer_3_0_0); String expected = "LwM2mSingleResource [id=0"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); + assertTrue(rpcActualResult.contains(expected)); } /** @@ -143,15 +123,14 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe_Ver() throws Exception { - String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String)path).contains("_")).findFirst().get(); + String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String) path).contains("_")).findFirst().get(); LwM2mPath expectedPath = new LwM2mPath(expectedInstance); int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); String ver = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().version; String expectedId = "/" + expectedPath.getObjectId() + "_" + Version.MAX + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource; - String actualResult = sendRpcObserve("Observe", expectedId); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expectedId); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: " + ver; + String expected = "Specified resource id " + expectedId + " is not valid version! Must be version: " + ver; assertEquals(expected, rpcActualResult.get("error").asText()); } @@ -162,10 +141,9 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception { - String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + ACCESS_CONTROL)).findFirst().get(); + String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + ACCESS_CONTROL)).findFirst().get(); String expected = objectInstanceIdVer + "/" + OBJECT_INSTANCE_ID_0; - String actualResult = sendRpcObserve("Observe", expected); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expected); assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); } @@ -177,8 +155,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception { String expected = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3; - String actualResult = sendRpcObserve("Observe", expected); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expected); String expectedValue = "value MUST NOT be null"; assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(expectedValue, rpcActualResult.get("error").asText()); @@ -191,9 +168,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveResourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception { String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_0; - sendRpcObserve("Observe", expectedId); - String actualResult = sendRpcObserve("Observe", expectedId); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expectedId); assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); } @@ -204,9 +179,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveExecuteResource_Result_METHOD_NOT_ALLOWED() throws Exception { String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; - sendRpcObserve("Observe", expectedId); - String actual = sendRpcObserve("Observe", expectedId); - ObjectNode rpcActual = JacksonUtil.fromString(actual, ObjectNode.class); + ObjectNode rpcActual = sendRpcObserveWithResult("Observe", expectedId); assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActual.get("result").asText()); } @@ -217,15 +190,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveRepeatedRequestObserveOnDevice_Result_CONTENT_PutIfAbsent() throws Exception { - String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - String actualResult = sendRpcObserve("Observe", idVer_3_0_0); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - actualResult = sendRpcObserve("Observe", idVer_3_0_0); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + sendRpcObserveWithResultValue("Observe", idVer_3_0_0); + String rpcActualResult = sendRpcObserveWithResultValue("Observe", idVer_3_0_0); String expected = "LwM2mSingleResource [id=0"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); + assertTrue(rpcActualResult.contains(expected)); } /** @@ -236,20 +204,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserve_Result_CONTENT_ONE_PATH_PreviousObservation_CONTAINCE_OTHER_CurrentObservation() throws Exception { sendCancelObserveAllWithAwait(deviceId); - // "3/0/9" - String idVer_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - String actualResult3_0_9 = sendRpcObserve("Observe", idVer_3_0_9); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult3_0_9, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // "3" - String actualResult3 = sendRpcObserve("Observe", objectIdVer_3); - rpcActualResult = JacksonUtil.fromString(actualResult3, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // PreviousObservation "3/0/9" change to CurrentObservation "3" - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); + // "3/0/9" + sendRpcObserveWithResultValue("Observe", idVer_3_0_9); + // "3" + sendRpcObserveWithResultValue("Observe", objectIdVer_3); + // PreviousObservation "3/0/9" change to CurrentObservation "3" + String actualValuesReadAll = sendRpcObserveWithResultValue("ObserveReadAll", null); assertEquals(1, actualValuesReadAll.split(",").length); String expected = "\"SingleObservation:/3\""; assertTrue(actualValuesReadAll.contains(expected)); @@ -263,28 +223,17 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserve_Result_CONTENT_ONE_PATH_CurrentObservation_CONTAINCE_OTHER_PreviousObservation() throws Exception { sendCancelObserveAllWithAwait(deviceId); + // "3" + sendRpcObserveWithResultValue("Observe", objectIdVer_3); + // "3/0/0"; WARN: - Token collision ? existing observation [/3] includes input observation [/3/0/0] + sendRpcObserveWithResultValue("Observe", idVer_3_0_0); - // "3" - String actualResult3 = sendRpcObserve("Observe", objectIdVer_3); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult3, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - - // "3/0/0"; WARN: - Token collision ? existing observation [/3] includes input observation [/3/0/0] - String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - String actualResult3_0_0 = sendRpcObserve("Observe", idVer_3_0_0); - rpcActualResult = JacksonUtil.fromString(actualResult3_0_0, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); + String actualValuesReadAll = sendRpcObserveWithResultValue("ObserveReadAll", null); assertEquals(1, actualValuesReadAll.split(",").length); String expected = "\"SingleObservation:/3\""; assertTrue(actualValuesReadAll.contains(expected)); } - /** * Observe {"id":"/3/0/9"} * ObserveCancel {"id":"/3/0/9"} @@ -293,24 +242,15 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO public void testObserveResource_ObserveCancelResource_Result_CONTENT_Count_1() throws Exception { sendCancelObserveAllWithAwait(deviceId); - String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - sendRpcObserve("Observe", expectedId_3_0_9); - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); + String actualValuesReadAll = sendRpcObserveReadAllWithResult(idVer_3_0_9); assertEquals(1, actualValuesReadAll.split(",").length); - String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\""; + String expected = "\"SingleObservation:" + id_3_0_9 + "\""; assertTrue(actualValuesReadAll.contains(expected)); // cancel observe "/3_1.2/0/9" - String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("1", rpcActualResult.get("value").asText()); + sendRpcObserveWithResultValue("ObserveCancel", idVer_3_0_9); } - /** * Observe {"id":"/3"} * ObserveCancel {"id":"/3/0/9"} -> INTERNAL_SERVER_ERROR @@ -320,29 +260,19 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO public void testObserveObject_ObserveCancelOneResource_Result_INTERNAL_SERVER_ERROR_Than_Cancel_ObserveObject_Result_CONTENT_Count_1() throws Exception { sendCancelObserveAllWithAwait(deviceId); - String expectedId_3 = objectIdVer_3; - sendRpcObserve("Observe", expectedId_3); - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); + String actualValuesReadAll = sendRpcObserveReadAllWithResult(objectIdVer_3); assertEquals(1, actualValuesReadAll.split(",").length); - String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3) + "\""; + String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(objectIdVer_3) + "\""; assertTrue(actualValuesReadAll.contains(expected)); - // cancel observe "/3_1.2/0/9" - String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9); - String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + fromVersionedIdToObjectId(expectedId_3_0_9); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + // cancel observe "/3_1.2/0/9" + ObjectNode rpcActualResult = sendRpcObserveWithResult("ObserveCancel", idVer_3_0_9); + String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + id_3_0_9; assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertTrue(rpcActualResult.get("error").asText().contains(expectedValue)); - // cancel observe "/3_1.2" - actualResult = sendRpcObserve("ObserveCancel", expectedId_3); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("1", rpcActualResult.get("value").asText()); + // cancel observe "/3_1.2" + sendRpcObserveWithResultValue("ObserveCancel", objectIdVer_3); } /** @@ -353,29 +283,73 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveResource_ObserveCancelObject_Result_CONTENT_Count_1() throws Exception { sendCancelObserveAllWithAwait(deviceId); - String expectedId_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - sendRpcObserve("Observe", expectedId_3_0_0); - String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - sendRpcObserve("Observe", expectedId_3_0_9); - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + sendRpcObserveWithWithTwoResource(idVer_3_0_0, idVer_3_0_9); + String rpcActualResul = sendRpcObserveWithResultValue("ObserveReadAll", null); + assertEquals(2, rpcActualResul.split(",").length); + String expected_3_0_0 = "\"SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_0) + "\""; + String expected_3_0_9 = "\"SingleObservation:" + id_3_0_9 + "\""; + assertTrue(rpcActualResul.contains(expected_3_0_0)); + assertTrue(rpcActualResul.contains(expected_3_0_9)); + + // cancel observe "/3_1.2" + String expectedId_3 = objectIdVer_3; + String rpcActualResult = sendRpcObserveWithResultValue("ObserveCancel", expectedId_3); + assertEquals("2", rpcActualResult); + } + + /** + * ObserveCancelAll + * Observe {"id":"3_1.2/0/9"} + * updateRegistration + * idResources_3_1.2/0/9 => updateAttrTelemetry >= 10 times + */ + @Test + public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { + sendCancelObserveAllWithAwait(deviceId); + + int cntUpdate = 3; + verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) + .updatedReg(Mockito.any(Registration.class)); + + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); + + cntUpdate = 10; + verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) + .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9)); + } + + private void sendRpcObserveWithWithTwoResource(String expectedId_1, String expectedId_2) throws Exception { + sendRpcObserve("Observe", expectedId_1); + sendRpcObserve("Observe", expectedId_2); + } + + private String sendRpcObserveReadAllWithResult(String params) throws Exception { + sendRpcObserve("Observe", params); + ObjectNode rpcActualResult = sendRpcObserveWithResult("ObserveReadAll", null); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); - assertEquals(2, actualValuesReadAll.split(",").length); - String expected_3_0_0 = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_0) + "\""; - String expected_3_0_9 = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\""; - assertTrue(actualValuesReadAll.contains(expected_3_0_0)); - assertTrue(actualValuesReadAll.contains(expected_3_0_9)); + return rpcActualResult.get("value").asText(); + } - // cancel observe "/3_1.2" - String expectedId_3 = objectIdVer_3; - String actualResult = sendRpcObserve("ObserveCancel", expectedId_3); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + private ObjectNode sendRpcObserveWithResult(String method, String params) throws Exception { + String actualResultReadAll = sendRpcObserve(method, params); + return JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + } + + private String sendRpcObserveWithResultValue(String method, String params) throws Exception { + String actualResultReadAll = sendRpcObserve(method, params); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("2", rpcActualResult.get("value").asText()); + return rpcActualResult.get("value").asText(); + } + + private void sendRpcObserveWithContainsLwM2mSingleResource(String params) throws Exception { + String rpcActualResult = sendRpcObserveWithResultValue("Observe", params); + assertTrue(rpcActualResult.contains("LwM2mSingleResource")); + assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); } private String sendRpcObserve(String method, String params) throws Exception { return sendObserve(method, params, deviceId); } } + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java index 138d0d6af8..8fae6a478e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java @@ -15,25 +15,16 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.Token; -import org.eclipse.californium.core.network.RandomTokenGenerator; import org.eclipse.californium.core.network.TokenGenerator; -import org.eclipse.californium.core.network.TokenGenerator.Scope; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.ResourceModel; -import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.peer.LwM2mIdentity; -import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.ExpirationListener; @@ -41,14 +32,12 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -60,13 +49,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import static org.eclipse.leshan.core.californium.ObserveUtil.CTX_CF_OBERSATION; -import static org.eclipse.leshan.core.californium.ObserveUtil.extractSerializedObservation; - @Slf4j public class TbInMemoryRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { @@ -108,7 +93,7 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable this.schedExecutor = schedExecutor; this.cleanPeriod = cleanPeriodInSec; this.modelProvider = modelProvider; - this.config = config; + this.config = config; } /* *************** Leshan Registration API **************** */ @@ -255,138 +240,60 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable List removed = new ArrayList<>(); try { lock.writeLock().lock(); - if (!regsByRegId.containsKey(registrationId)) { throw new IllegalStateException(String.format( "can not add observation %s there is no registration with id %s", observation, registrationId)); } - - if (observation instanceof SingleObservation) { - if (validateObserveResource(((SingleObservation)observation).getPath(), registrationId)) { - updateSingleObservation(registrationId, (SingleObservation) observation, addIfAbsent, removed); - // cancel existing observations for the same path and registration id. - cancelObservation (observation, registrationId, removed); - } - } else { - ContentFormat ct = ((CompositeObservation) observation).getResponseContentFormat(); - Map ctx = observation.getContext(); - String serializedObservation = extractSerializedObservation(observation); - JsonNode nodeSerObs = JacksonUtil.toJsonNode(serializedObservation); - ((CompositeObservation)observation).getPaths().forEach(path -> { - if (validateObserveResource(path, registrationId)) { - String serializedObs = createSerializedSingleObservation(nodeSerObs, path.toString()); - Observation singleObservation = createSingleObservation(registrationId, path, ct, ctx, serializedObs, getTokenGenerator()); - updateSingleObservation(registrationId, (SingleObservation) singleObservation, addIfAbsent, removed); - // cancel existing observations for the same path and registration id. - cancelObservation (singleObservation, registrationId, removed); - } - }); - } - + updateObservation(registrationId, observation, addIfAbsent, removed); } finally { lock.writeLock().unlock(); } - return removed; } - private boolean validateObserveResource(LwM2mPath path, String registrationId){ - // check if the resource is readable. - if (path.isResource() || path.isResourceInstance()) { - ObjectModel objectModel = modelProvider.getObjectModel(getRegistration(registrationId)).getObjectModel(path.getObjectId()); - ResourceModel resourceModel = objectModel == null ? null : objectModel.resources.get(path.getResourceId()); - if (resourceModel == null) { - return false; - } else if (!resourceModel.operations.isReadable()) { - return false; - } else if (path.isResourceInstance() && !resourceModel.multiple) { - return false; - } - } - return true; - } - - private void updateSingleObservation (String registrationId, SingleObservation observation, boolean addIfAbsent, List removed) { - // Absorption by existing Observations - + private void updateObservation(String registrationId, Observation observation, boolean addIfAbsent, List removed) { + // Absorption by existing Observations Observation previousObservation = null; - SingleObservation existingObservation = null; - ObservationIdentifier id = observation.getId(); if (addIfAbsent) { if (!obsByToken.containsKey(id)) { - existingObservation = validateByAbsorptionExistingObservations(observation); - if (existingObservation == null) { - obsByToken.put(id, observation); - } else if (!existingObservation.getPath().equals(observation.getPath())){ - obsByToken.put(id, observation); - previousObservation = obsByToken.get(existingObservation.getId()); - } + previousObservation = obsByToken.put(id, observation); + } else { + obsByToken.put(id, observation); } } else { previousObservation = obsByToken.put(id, observation); } + if (!tokensByRegId.containsKey(registrationId)) { tokensByRegId.put(registrationId, new HashSet()); } - - if (existingObservation == null || !existingObservation.getPath().equals(observation.getPath())) { - tokensByRegId.get(registrationId).add(id); - } + tokensByRegId.get(registrationId).add(id); // log any collisions - if (addIfAbsent && previousObservation != null) { - if (!existingObservation.getPath().equals(observation.getPath())) { - removed.add(previousObservation); - log.warn("Token collision ? observation [{}] will be replaced by observation [{}], that this observation includes input observation [{}]!", - previousObservation, observation, observation); - } else { - log.warn("Token collision ? existing observation [{}] includes input observation [{}]", - existingObservation, observation); - } + if (previousObservation != null) { + removed.add(previousObservation); + log.warn("Token collision ? observation [{}] will be replaced by observation [{}] ", + previousObservation, observation); } - } - - private SingleObservation validateByAbsorptionExistingObservations (SingleObservation observation) { - LwM2mPath pathObservation = observation.getPath(); - AtomicReference result = new AtomicReference<>(); - obsByToken.values().stream().forEach(obs -> { - LwM2mPath pathObs = ((SingleObservation)obs).getPath(); - if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObs = "3/0/9"-> pathObservation = "3" - (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { - result.set((SingleObservation)obs); - } else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObs = "3" -> pathObservation = "3/0/9" - result.set(observation); + // cancel existing observations for the same path and registration id. + for (Observation obs : unsafeGetObservations(registrationId)) { + if (areTheSamePaths(observation, obs) && !observation.getId().equals(obs.getId())) { + unsafeRemoveObservation(obs.getId()); + removed.add(obs); } - }); - return result.get(); - } - - private TokenGenerator getTokenGenerator(){ - if (this.tokenGenerator == null) { - this.tokenGenerator = new RandomTokenGenerator(config.getCoapConfig()); } - return this.tokenGenerator; } - public static SingleObservation createSingleObservation(String registrationId, LwM2mPath target, ContentFormat ct, - Map ctx, String serializedObservation, TokenGenerator tokenGenerator) { - Token token = tokenGenerator.createToken(Scope.SHORT_TERM); - Map protocolData = Collections.emptyMap(); - if (serializedObservation != null) { - protocolData = new HashMap<>(); - protocolData.put(CTX_CF_OBERSATION, serializedObservation); + private boolean areTheSamePaths(Observation observation, Observation obs) { + if (observation instanceof SingleObservation && obs instanceof SingleObservation) { + return ((SingleObservation) observation).getPath().equals(((SingleObservation) obs).getPath()); } - return new SingleObservation(new ObservationIdentifier(token.getBytes()), registrationId, target, ct, ctx, protocolData); - } - - public static String createSerializedSingleObservation(JsonNode nodeSerObs, String path){ - if (nodeSerObs.has("context")){ - ((ObjectNode) nodeSerObs.get("context")).put("leshan-path", path + "\n"); - return JacksonUtil.toString(nodeSerObs); + if (observation instanceof CompositeObservation && obs instanceof CompositeObservation) { + return ((CompositeObservation) observation).getPaths().equals(((CompositeObservation) obs).getPaths()); } - return null; + return false; } @Override @@ -470,24 +377,6 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable return obs; } - private void cancelObservation (Observation observation, String registrationId, List removed) { - for (Observation obs : unsafeGetObservations(registrationId)) { - cancelExistingObservation(observation, obs, removed); - } - } - - private void cancelExistingObservation(Observation observation, Observation obs, List removed) { - LwM2mPath pathObservation = ((SingleObservation)observation).getPath(); - LwM2mPath pathObs = ((SingleObservation)obs).getPath(); - if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObservation = "3", pathObs = "3/0/9" - (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { - unsafeRemoveObservation(obs.getId()); - removed.add(obs); - } else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObservation = "3/0/9", pathObs = "3" - unsafeRemoveObservation(observation.getId()); - } - } - private void unsafeRemoveObservation(ObservationIdentifier observationId) { Observation removed = obsByToken.remove(observationId); if (removed != null) { @@ -500,7 +389,6 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable } } - /** * CancelAllObservation * @param registrationId diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index b52764187d..cdd70e61a5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.network.RandomTokenGenerator; @@ -33,10 +32,8 @@ import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.peer.LwM2mIdentity; -import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.redis.RedisRegistrationStore; import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; @@ -45,15 +42,12 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.integration.redis.util.RedisLockRegistry; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; @@ -65,19 +59,14 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.eclipse.leshan.core.californium.ObserveUtil.extractSerializedObservation; -import static org.thingsboard.server.transport.lwm2m.server.store.TbInMemoryRegistrationStore.createSerializedSingleObservation; -import static org.thingsboard.server.transport.lwm2m.server.store.TbInMemoryRegistrationStore.createSingleObservation; @Slf4j public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { @@ -87,8 +76,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab /** Defaut Extra time for registration lifetime in seconds */ public static final long DEFAULT_GRACE_PERIOD = 0; - private static final Logger LOG = LoggerFactory.getLogger(RedisRegistrationStore.class); - // Redis key prefixes public static final String REG_EP = "REG:EP:"; // (Endpoint => Registration) private static final String REG_EP_REGID_IDX = "EP:REGID:"; // secondary index key (Registration ID => Endpoint) @@ -131,7 +118,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab public TbLwM2mRedisRegistrationStore(LwM2MTransportServerConfig config, RedisConnectionFactory connectionFactory, long cleanPeriodInSec, long lifetimeGracePeriodInSec, int cleanLimit, LwM2mVersionedModelProvider modelProvider) { this(config, connectionFactory, Executors.newScheduledThreadPool(1, - new NamedThreadFactory(String.format("RedisRegistrationStore Cleaner (%ds)", cleanPeriodInSec))), + new NamedThreadFactory(String.format("RedisRegistrationStore Cleaner (%ds)", cleanPeriodInSec))), cleanPeriodInSec, lifetimeGracePeriodInSec, cleanLimit, modelProvider); } @@ -281,6 +268,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab return getRegistration(connection, registrationId); } } + private Registration getRegistration(RedisConnection connection, String registrationId) { byte[] ep = connection.get(toRegIdKey(registrationId)); if (ep == null) { @@ -494,31 +482,10 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab Lock lock = null; String lockKey = toLockKey(ep); - try { lock = redisLock.obtain(lockKey); lock.lock(); - if (observation instanceof SingleObservation) { - if (validateObserveResource(((SingleObservation)observation).getPath(), registrationId)) { - updateSingleObservation(registrationId, (SingleObservation)observation, addIfAbsent, removed, connection); - // cancel existing observations for the same path and registration id. - cancelObservation(observation, registrationId, removed, connection); - } - } else { - ContentFormat ct = ((CompositeObservation) observation).getResponseContentFormat(); - Map ctx = observation.getContext(); - String serializedObservation = extractSerializedObservation(observation); - JsonNode nodeSerObs = JacksonUtil.toJsonNode(serializedObservation); - ((CompositeObservation)observation).getPaths().forEach(path -> { - if (validateObserveResource(path, registrationId)) { - String serializedObs = createSerializedSingleObservation(nodeSerObs, path.toString()); - SingleObservation singleObservation = createSingleObservation(registrationId, path, ct, ctx, serializedObs, getTokenGenerator()); - updateSingleObservation(registrationId, singleObservation, addIfAbsent, removed, connection); - // cancel existing observations for the same path and registration id. - cancelObservation (singleObservation, registrationId, removed, connection); - } - }); - } + updateObservation(registrationId, observation, addIfAbsent, removed, connection); } finally { if (lock != null) { lock.unlock(); @@ -527,43 +494,21 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } return removed; } - - private boolean validateObserveResource(LwM2mPath path, String registrationId){ - // check if the resource is readable. - if (path.isResource() || path.isResourceInstance()) { - ObjectModel objectModel = modelProvider.getObjectModel(getRegistration(registrationId)).getObjectModel(path.getObjectId()); - ResourceModel resourceModel = objectModel == null ? null : objectModel.resources.get(path.getResourceId()); - if (resourceModel == null) { - return false; - } else if (!resourceModel.operations.isReadable()) { - return false; - } else if (path.isResourceInstance() && !resourceModel.multiple) { - return false; - } - } - return true; - } - - private void updateSingleObservation (String registrationId, SingleObservation observation, boolean addIfAbsent, - List removed, RedisConnection connection) { + private void updateObservation(String registrationId, Observation observation, boolean addIfAbsent, + List removed, RedisConnection connection) { // Add and Get previous observation byte[] previousValue; byte[] key = toKey(OBS_TKN, observation.getId().getBytes()); byte[] serializeObs = serializeObs(observation); // we analyze the present previous value - SingleObservation existingObservation = null; - - if (addIfAbsent){ + if (addIfAbsent) { previousValue = connection.stringCommands().get(key); if (previousValue == null) { - existingObservation = validateByAbsorptionExistingObservations(observation, connection); - if (existingObservation == null){ - connection.stringCommands().set(key, serializeObs); - } else if(!existingObservation.getPath().equals(observation.getPath())) { - connection.stringCommands().set(key, serializeObs); - previousValue = serializeObs(existingObservation); - } + connection.stringCommands().set(key, serializeObs); + previousValue = serializeObs; + } else { + connection.stringCommands().set(key, serializeObs); } } else { previousValue = connection.stringCommands().getSet(key, serializeObs); @@ -573,25 +518,38 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab connection.listCommands().lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), observation.getId().getBytes()); // log any collisions - if (addIfAbsent && previousValue != null) { - if (!existingObservation.getPath().equals(observation.getPath())) { - Observation previousObservation = deserializeObs(previousValue); - removed.add(previousObservation); - LOG.warn("Token collision ? observation [{}] will be replaced by observation [{}], that this observation includes input observation [{}]!", - previousObservation, observation, observation); - } else { - LOG.warn("Token collision ? existing observation [{}] includes input observation [{}]", - existingObservation, observation); + Observation previousObservation; + if (previousValue != null && previousValue.length != 0) { + previousObservation = deserializeObs(previousValue); + log.warn("Token collision ? observation [{}] will be replaced by observation [{}] ", + previousObservation, observation); + } + + // cancel existing observations for the same path and registration id. + for (Observation obs : getObservations(connection, registrationId)) { + if (areTheSamePaths(observation, obs) && !observation.getId().equals(obs.getId())) { + removed.add(obs); + unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes()); } } } + private boolean areTheSamePaths(Observation observation, Observation obs) { + if (observation instanceof SingleObservation && obs instanceof SingleObservation) { + return ((SingleObservation) observation).getPath().equals(((SingleObservation) obs).getPath()); + } + if (observation instanceof CompositeObservation && obs instanceof CompositeObservation) { + return ((CompositeObservation) observation).getPaths().equals(((CompositeObservation) obs).getPaths()); + } + return false; + } @Override public Collection getObservations(String registrationId) { try (var connection = connectionFactory.getConnection()) { return getObservations(connection, registrationId); } } + @Override public Observation getObservation(String registrationId, ObservationIdentifier observationId) { return getObservations(registrationId).stream().filter( @@ -654,25 +612,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab return result; } - private SingleObservation validateByAbsorptionExistingObservations(SingleObservation observation, RedisConnection connection) { - LwM2mPath pathObservation = observation.getPath(); - AtomicReference result = new AtomicReference<>(); - Collection observations = getObservations(connection, observation.getRegistrationId()); - observations.stream().forEach(obs -> { - LwM2mPath pathObs = ((SingleObservation)obs).getPath(); - if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObs = "3/0/9"-> pathObservation = "3" - (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { - result.set((SingleObservation)obs); - } else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObs = "3" -> pathObservation = "3/0/9" - result.set(observation); - } - }); - return result.get(); - - - } - - @Override public Collection removeObservations(String registrationId) { try (var connection = connectionFactory.getConnection()) { @@ -710,7 +649,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab /* *************** Observation utility functions **************** */ - private TokenGenerator getTokenGenerator(){ + private TokenGenerator getTokenGenerator() { if (this.tokenGenerator == null) { this.tokenGenerator = new RandomTokenGenerator(config.getCoapConfig()); } @@ -751,8 +690,8 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } private void cancelExistingObservation(RedisConnection connection, Observation observation, Observation obs, List removed) { - LwM2mPath pathObservation = ((SingleObservation)observation).getPath(); - LwM2mPath pathObs = ((SingleObservation)obs).getPath(); + LwM2mPath pathObservation = ((SingleObservation) observation).getPath(); + LwM2mPath pathObs = ((SingleObservation) obs).getPath(); if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObservation = "3", pathObs = "3/0/9" (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { unsafeRemoveObservation(connection, obs.getRegistrationId(), obs.getId().getBytes()); @@ -803,7 +742,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab try { schedExecutor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { - LOG.warn("Destroying RedisRegistrationStore was interrupted.", e); + log.warn("Destroying RedisRegistrationStore was interrupted.", e); } } @@ -824,7 +763,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } } } catch (Exception e) { - LOG.warn("Unexpected Exception while registration cleaning", e); + log.warn("Unexpected Exception while registration cleaning", e); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index 5be27be03d..61d751d64d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -372,21 +372,19 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl LwM2mPath path = instant.getKey(); LwM2mNode node = instant.getValue(); LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); - String stringPath = convertObjectIdToVersionedId(path.toString(), lwM2MClient); - ObjectModel objectModelVersion = lwM2MClient.getObjectModel(stringPath, modelProvider); + ObjectModel objectModelVersion = lwM2MClient.getObjectModel(path.toString(), modelProvider); if (objectModelVersion != null) { if (node instanceof LwM2mObject) { LwM2mObject lwM2mObject = (LwM2mObject) node; - this.updateObjectResourceValue(lwM2MClient, lwM2mObject, stringPath, 0); + this.updateObjectResourceValue(lwM2MClient, lwM2mObject, path.toString(), 0); } else if (node instanceof LwM2mObjectInstance) { LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) node; - this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, stringPath, 0); + this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path.toString(), 0); } else if (node instanceof LwM2mResource) { LwM2mResource lwM2mResource = (LwM2mResource) node; - this.updateResourcesValue(lwM2MClient, lwM2mResource, stringPath, Mode.UPDATE, 0); + this.updateResourcesValue(lwM2MClient, lwM2mResource, path.toString(), Mode.UPDATE, 0); } } - tryAwake(lwM2MClient); } } @@ -569,7 +567,6 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl } private void updateObjectInstanceResourceValue(LwM2mClient client, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer, int code) { - LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { String pathRez = pathIdVer + "/" + resourceId; this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE, code); @@ -584,11 +581,12 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl * #4 updateAttrTelemetry * @param lwM2MClient - Registration LwM2M Client * @param lwM2mResource - LwM2mSingleResource response.getContent() - * @param path - resource + * @param stringPath - resource * @param mode - Replace, Update */ - private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path, Mode mode, int code) { + private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String stringPath, Mode mode, int code) { Registration registration = lwM2MClient.getRegistration(); + String path = convertObjectIdToVersionedId(stringPath, lwM2MClient); if (lwM2MClient.saveResourceValue(path, lwM2mResource, modelProvider, mode)) { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, lwM2MClient))) { otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); 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 e2e31f2768..6b90ae21b6 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 @@ -156,21 +156,19 @@ public class LwM2MTransportUtil { } public static String convertObjectIdToVersionedId(String path, LwM2mClient lwM2MClient) { - String ver = String.valueOf(lwM2MClient.getSupportedObjectVersion(new LwM2mPath(path).getObjectId())); - return convertObjectIdToVerId(path, ver); - } - public static String convertObjectIdToVerId(String path, String ver) { - ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); - try { - String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); - if (keyArray.length > 1) { - keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver; + String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); + if (keyArray.length > 1) { + try { + Integer objectId = Integer.valueOf((keyArray[1].split(LWM2M_SEPARATOR_KEY))[0]); + String ver = String.valueOf(lwM2MClient.getSupportedObjectVersion(objectId)); + ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); + keyArray[1] = String.valueOf(keyArray[1]).contains(LWM2M_SEPARATOR_KEY) ? keyArray[1] : keyArray[1] + LWM2M_SEPARATOR_KEY + ver; return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); - } else { - return path; + } catch (Exception e) { + return null; } - } catch (Exception e) { - return null; + } else { + return path; } } @@ -214,20 +212,20 @@ 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); + String valueJsonStr = JacksonUtil.toString(value); + JsonElement element = JsonUtils.parse(valueJsonStr); + return convertMultiResourceValuesFromJson(element, type, versionedId); } 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)); + newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource(obj.getValue().getAsString(), type, versionedId)); }); return newValues; } - public static Object convertValueByTypeResource (String value, ResourceModel.Type type, String versionedId) { + public static Object convertValueByTypeResource(String value, ResourceModel.Type type, String versionedId) { return LwM2mValueConverterImpl.getInstance().convertValue(value, STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); } @@ -356,7 +354,7 @@ public class LwM2MTransportUtil { serverCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH); serverCoapConfig.setTransient(DTLS_CONNECTION_ID_NODE_ID); serverCoapConfig.set(DTLS_CONNECTION_ID_LENGTH, cIdLength); - if ( cIdLength > 4) { + if (cIdLength > 4) { serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, 0); } else { serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, null); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java index c76627d3a1..87d466e11a 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java @@ -16,6 +16,7 @@ package org.thingsboard.server.msa; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.restassured.RestAssured; import io.restassured.common.mapper.TypeRef; import io.restassured.config.HeaderConfig; @@ -116,6 +117,16 @@ public class TestRestClient { .as(Device.class); } + public ObjectNode postRpcLwm2mParams(String deviceIdStr, String body) { + return given().spec(requestSpec).body(body) + .post("/api/plugins/rpc/twoway/" + deviceIdStr) + .then() + .statusCode(HTTP_OK) + .extract() + .as(ObjectNode.class); + } + + public Device getDeviceByName(String deviceName) { return given().spec(requestSpec).pathParam("deviceName", deviceName) .get("/api/tenant/devices?deviceName={deviceName}") diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java similarity index 50% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java index c0ae8e9776..b60eb15b5c 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java @@ -13,12 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa; +package org.thingsboard.server.msa.connectivity.lwm2m; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.eclipse.leshan.client.object.Security; +import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.util.Hex; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.Device; @@ -42,12 +48,14 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.msa.connectivity.lwm2m.LwM2MTestClient; -import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState; +import org.thingsboard.server.msa.AbstractContainerTest; +import org.thingsboard.server.msa.connectivity.lwm2m.client.LwM2MTestClient; +import org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState; import java.net.ServerSocket; import java.nio.charset.StandardCharsets; @@ -55,6 +63,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -63,45 +72,45 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.client.object.Security.psk; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_ENDPOINT_NO_SEC; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_ENDPOINT_PSK; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_LWM2M_SETTINGS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_PSK_IDENTITY; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_PSK_KEY; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.OBSERVE_ATTRIBUTES_WITHOUT_PARAMS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.SECURE_URI; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.SECURITY_NO_SEC; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.resources; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.shortServerId; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.testng.AssertJUnit.assertEquals; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_ENDPOINT_PSK; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_LWM2M_SETTINGS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_PSK_IDENTITY; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_PSK_KEY; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_INIT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.OBSERVE_ATTRIBUTES_WITH_PARAMS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.SECURE_URI; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.SECURITY_NO_SEC; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.resources; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.shortServerId; @Slf4j -public class AbstractLwm2mClientTest extends AbstractContainerTest{ +public class AbstractLwm2mClientTest extends AbstractContainerTest { protected ScheduledExecutorService executor; protected Security security; protected final PageLink pageLink = new PageLink(30); protected TenantId tenantId; - protected DeviceProfile lwm2mDeviceProfile; - protected Device lwM2MDeviceTest; - protected LwM2MTestClient lwM2MTestClient; public final Set expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); - public void connectLwm2mClientNoSec() throws Exception { - LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(CLIENT_ENDPOINT_NO_SEC)); - basicTestConnection(SECURITY_NO_SEC, - deviceCredentials, - CLIENT_ENDPOINT_NO_SEC, - "TestConnection Lwm2m NoSec (msa)"); + public void createLwm2mDevicesForConnectNoSec(String name, Lwm2mDevicesForTest devicesForTest) throws Exception { + String clientEndpoint = name + "-" + RandomStringUtils.randomAlphanumeric(7); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); + Device lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint, devicesForTest.getLwm2mDeviceProfile().getId()); + LwM2MTestClient lwM2MTestClient = createNewClient(SECURITY_NO_SEC, clientEndpoint, executor); + devicesForTest.setLwM2MDeviceTest(lwM2MDeviceTest); + devicesForTest.setLwM2MTestClient(lwM2MTestClient); } - public void connectLwm2mClientPsk() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_PSK; + public void createLwm2mDevicesForConnectPsk(Lwm2mDevicesForTest devicesForTest) throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_PSK +"-" + RandomStringUtils.randomAlphanumeric(7); String identity = CLIENT_PSK_IDENTITY; String keyPsk = CLIENT_PSK_KEY; PSKClientCredential clientCredentials = new PSKClientCredential(); @@ -113,40 +122,64 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ identity.getBytes(StandardCharsets.UTF_8), Hex.decodeHex(keyPsk.toCharArray())); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecurePsk(clientCredentials); + Device lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint, devicesForTest.getLwm2mDeviceProfile().getId()); + LwM2MTestClient lwM2MTestClient = createNewClient(security, clientEndpoint, executor); + devicesForTest.setLwM2MDeviceTest(lwM2MDeviceTest); + devicesForTest.setLwM2MTestClient(lwM2MTestClient); + } - basicTestConnection(security, - deviceCredentials, - clientEndpoint, - "TestConnection Lwm2m Rpc (msa)"); + public void observeResource_Update_AfterUpdateRegistration_test(LwM2MTestClient lwM2MTestClient, String deviceIdStr) throws Exception { + awaitUpdateRegistrationSuccess(lwM2MTestClient, 5); + sendCancelObserveAllWithAwait(deviceIdStr); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + String param = "/3_1.2/0/9"; + sendRpcObserveWithContainsLwM2mSingleResource(param, deviceIdStr); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + sendCancelObserveAllWithAwait(deviceIdStr); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + sendRpcObserveWithContainsLwM2mSingleResource(param, deviceIdStr); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 2); + } + public void observeCompositeResource_Update_AfterUpdateRegistration_test(LwM2MTestClient lwM2MTestClient, String deviceIdStr) throws Exception { + awaitUpdateRegistrationSuccess(lwM2MTestClient, 5); + sendCancelObserveAllWithAwait(deviceIdStr); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + String expectedKey3_0_9 = "batteryLevel"; +// String expectedKey3_0_14 = "UtfOffset"; +// String expectedKey19_0_0 = "dataRead"; +// String expectedKey19_1_0 = "dataWrite"; +// String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\", \"" + expectedKey3_0_9 + "\"]"; + String expectedKeys = "[\"" + expectedKey3_0_9 + "\"]"; + sendRpcObserveCompositeWithContainsLwM2mSingleResource(expectedKeys, deviceIdStr); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + sendCancelObserveAllWithAwait(deviceIdStr); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + sendRpcObserveCompositeWithContainsLwM2mSingleResource(expectedKeys, deviceIdStr); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 2); } - public void basicTestConnection(Security security, - LwM2MDeviceCredentials deviceCredentials, - String clientEndpoint, String alias) throws Exception { - // create lwm2mClient and lwM2MDevice - lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint); - lwM2MTestClient = createNewClient(security, clientEndpoint, executor); + public void basicTestConnection(LwM2MTestClient lwM2MTestClient, String alias) throws Exception { + LwM2MClientState finishState = ON_REGISTRATION_SUCCESS; + await(alias + " - " + ON_REGISTRATION_STARTED) + .atMost(40, TimeUnit.SECONDS) + .until(() -> { + log.warn("msa basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); + return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); + }); + await(alias + " - " + ON_UPDATE_SUCCESS) + .atMost(40, TimeUnit.SECONDS) + .until(() -> { + log.warn("msa basicTestConnection update -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); + return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS); + }); + assertThat(lwM2MTestClient.getClientStates()).containsAll(expectedStatusesRegistrationLwm2mSuccess); - LwM2MClientState finishState = ON_REGISTRATION_SUCCESS; - await(alias + " - " + ON_REGISTRATION_STARTED) - .atMost(40, TimeUnit.SECONDS) - .until(() -> { - log.warn("msa basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); - return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); - }); - await(alias + " - " + ON_UPDATE_SUCCESS) - .atMost(40, TimeUnit.SECONDS) - .until(() -> { - log.warn("msa basicTestConnection update -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); - return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS); - }); - assertThat(lwM2MTestClient.getClientStates()).containsAll(expectedStatusesRegistrationLwm2mSuccess); } public LwM2MTestClient createNewClient(Security security, String endpoint, ScheduledExecutorService executor) throws Exception { this.executor = executor; - LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(endpoint); + LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(executor, endpoint); try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); lwM2MTestClient.init(security, clientPort); @@ -154,16 +187,16 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ return lwM2MTestClient; } - protected void destroyAfter(){ - clientDestroy(); - deviceDestroy(); - deviceProfileDestroy(); + protected void destroyAfter(Lwm2mDevicesForTest devicesForTest){ + clientDestroy(devicesForTest.getLwM2MTestClient()); + deviceDestroy(devicesForTest.getLwM2MDeviceTest()); + deviceProfileDestroy(devicesForTest.getLwm2mDeviceProfile()); if (executor != null) { executor.shutdown(); } } - protected void clientDestroy() { + protected void clientDestroy(LwM2MTestClient lwM2MTestClient) { try { if (lwM2MTestClient != null) { lwM2MTestClient.destroy(); @@ -172,7 +205,7 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ log.error("Failed client Destroy", e); } } - protected void deviceDestroy() { + protected void deviceDestroy(Device lwM2MDeviceTest) { try { if (lwM2MDeviceTest != null) { testRestClient.deleteDeviceIfExists(lwM2MDeviceTest.getId()); @@ -182,13 +215,13 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ } } - protected void initTest(String deviceProfileName) throws Exception { + protected DeviceProfile initTest(String deviceProfileName) throws Exception { if (executor != null) { executor.shutdown(); } executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-scheduled-" + deviceProfileName)); - lwm2mDeviceProfile = getDeviceProfile(deviceProfileName); + DeviceProfile lwm2mDeviceProfile = getDeviceProfile(deviceProfileName); tenantId = lwm2mDeviceProfile.getTenantId(); for (String resourceName : resources) { @@ -201,6 +234,7 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ lwModel.setData(bytes); testRestClient.postTbResourceIfNotExists(lwModel); } + return lwm2mDeviceProfile; } protected DeviceProfile getDeviceProfile(String deviceProfileName) throws Exception { @@ -235,7 +269,7 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ return deviceProfile; } - protected void deviceProfileDestroy(){ + protected void deviceProfileDestroy(DeviceProfile lwm2mDeviceProfile){ try { if (lwm2mDeviceProfile != null) { testRestClient.deleteDeviceProfileIfExists(lwm2mDeviceProfile); @@ -245,17 +279,17 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ } } - protected Device createDeviceWithCredentials(LwM2MDeviceCredentials deviceCredentials, String clientEndpoint) throws Exception { - Device device = createDevice(deviceCredentials, clientEndpoint); + protected Device createDeviceWithCredentials(LwM2MDeviceCredentials deviceCredentials, String clientEndpoint, DeviceProfileId profileId) throws Exception { + Device device = createDevice(deviceCredentials, clientEndpoint, profileId); return device; } - protected Device createDevice(LwM2MDeviceCredentials credentials, String clientEndpoint) throws Exception { + protected Device createDevice(LwM2MDeviceCredentials credentials, String clientEndpoint, DeviceProfileId profileId) throws Exception { Device device = testRestClient.getDeviceByNameIfExists(clientEndpoint); if (device == null) { device = new Device(); device.setName(clientEndpoint); - device.setDeviceProfileId(lwm2mDeviceProfile.getId()); + device.setDeviceProfileId(profileId); device.setTenantId(tenantId); device = testRestClient.postDevice("", device); } @@ -288,7 +322,7 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration() { List bootstrapServerCredentials = new ArrayList<>(); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); - TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, TelemetryMappingConfiguration.class); + TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class); OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(CLIENT_LWM2M_SETTINGS, OtherConfiguration.class); transportConfiguration.setBootstrapServerUpdateEnable(true); transportConfiguration.setObserveAttr(observeAttrConfiguration); @@ -317,4 +351,75 @@ public class AbstractLwm2mClientTest extends AbstractContainerTest{ bootstrapCredentials.setLwm2mServer(serverCredentials); return bootstrapCredentials; } + + protected void sendCancelObserveAllWithAwait(String deviceIdStr) throws Exception { + ObjectNode rpcActualResultCancelAll = sendRpcObserve("ObserveCancelAll", null, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancelAll.get("result").asText()); + awaitObserveReadAll(0, deviceIdStr); + } + + protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { + await("ObserveReadAll after start client/test: countObserve " + cntObserve) + .atMost(40, TimeUnit.SECONDS) + .until(() -> cntObserve == getCntObserveAll(deviceIdStr)); + } + protected void awaitUpdateRegistrationSuccess(LwM2MTestClient lwM2MTestClient, int cntUpdate) throws Exception { + cntUpdate = cntUpdate + lwM2MTestClient.getCountUpdateRegistrationSuccess(); + int finalCntUpdate = cntUpdate; + await("Update Registration client: countUpdateSuccess " + finalCntUpdate) + .atMost(40, TimeUnit.SECONDS) + .until(() -> finalCntUpdate <= lwM2MTestClient.getCountUpdateRegistrationSuccess()); + } + protected void awaitObserveReadResource_3_0_9(int cntRead, String deviceIdStr) throws Exception { + await("Read value 3/0/9 after start observe: countRead " + cntRead) + .atMost(40, TimeUnit.SECONDS) + .until(() -> cntRead == getCntObserveAll(deviceIdStr)); + } + + protected Integer getCntObserveAll(String deviceIdStr) throws Exception { + ObjectNode rpcActualResultBefore = sendRpcObserve("ObserveReadAll", null, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); + JsonElement element = JsonParser.parseString(rpcActualResultBefore.get("value").asText()); + return element.isJsonArray() ? ((JsonArray)element).size() : null; + } + + private void sendRpcObserveWithContainsLwM2mSingleResource(String params, String deviceIdStr) throws Exception { + String rpcActualResult = sendRpcObserveWithResultValue(params, deviceIdStr); + assertTrue(rpcActualResult.contains("LwM2mSingleResource")); + assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceIdStr)).get()); + } + + private void sendRpcObserveCompositeWithContainsLwM2mSingleResource(String params, String deviceIdStr) throws Exception { + String rpcActualResult = sendRpcObserveCompositeWithResultValue(params, deviceIdStr); + assertTrue(rpcActualResult.contains("LwM2mSingleResource")); + assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceIdStr)).get()); + } + + private String sendRpcObserveWithResultValue(String params, String deviceIdStr) throws Exception { + ObjectNode rpcActualResult = sendRpcObserve("Observe", params, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + return rpcActualResult.get("value").asText(); + } + + private String sendRpcObserveCompositeWithResultValue(String params, String deviceIdStr) throws Exception { + ObjectNode rpcActualResult = sendRpcObserveComposite(params, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + return rpcActualResult.get("value").asText(); + } + + protected ObjectNode sendRpcObserve(String method, String params, String deviceIdStr) throws Exception { + String sendRpcRequest; + if (params == null) { + sendRpcRequest = "{\"method\": \"" + method + "\"}"; + } + else { + sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; + } + return testRestClient.postRpcLwm2mParams(deviceIdStr, sendRpcRequest); + } + protected ObjectNode sendRpcObserveComposite(String keys, String deviceIdStr) throws Exception { + String method = "ObserveComposite"; + String sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; + return testRestClient.postRpcLwm2mParams(deviceIdStr, sendRpcRequest); + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mDevicesForTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mDevicesForTest.java new file mode 100644 index 0000000000..5374449e11 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mDevicesForTest.java @@ -0,0 +1,20 @@ +package org.thingsboard.server.msa.connectivity.lwm2m; + + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.msa.connectivity.lwm2m.client.LwM2MTestClient; + +@Slf4j +@Data +public class Lwm2mDevicesForTest { + + Device lwM2MDeviceTest; + LwM2MTestClient lwM2MTestClient; + DeviceProfile lwm2mDeviceProfile; + public Lwm2mDevicesForTest(DeviceProfile deviceProfile) { + this.lwm2mDeviceProfile = deviceProfile; + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/FwLwM2MDevice.java similarity index 98% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/FwLwM2MDevice.java index 2910a2b7da..0037cff467 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/FwLwM2MDevice.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java similarity index 79% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java index c9f193704b..9fb9d4c67d 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -44,13 +44,14 @@ import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.StaticModel; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.UpdateRequest; -import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState; +import org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState; import java.io.IOException; import java.net.InetSocketAddress; @@ -60,6 +61,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; @@ -70,33 +72,34 @@ import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_TIMEOUT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_FAILURE; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_TIMEOUT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_EXPECTED_ERROR; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_FAILURE; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_TIMEOUT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_FAILURE; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_TIMEOUT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.resources; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.serverId; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.shortServerId; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_TIMEOUT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_FAILURE; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_TIMEOUT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_EXPECTED_ERROR; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_INIT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_FAILURE; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_TIMEOUT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_FAILURE; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_TIMEOUT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.resources; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.serverId; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.shortServerId; @Slf4j @Data public class LwM2MTestClient { + private final ScheduledExecutorService executor; private final String endpoint; private LeshanClient leshanClient; private SimpleLwM2MDevice lwM2MDevice; @@ -106,6 +109,9 @@ public class LwM2MTestClient { private FwLwM2MDevice fwLwM2MDevice; private Map clientDtlsCid; + private int countUpdateRegistrationSuccess; + private int countReadObserveAfterUpdateRegistrationSuccess; + public void init(Security security, int clientPort) throws InvalidDDFFileException, IOException { assertThat(leshanClient).as("client already initialized").isNull(); @@ -123,7 +129,8 @@ public class LwM2MTestClient { lwm2mServer.setId(serverId); initializer.setInstancesForObject(SERVER, lwm2mServer); - initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice()); + SimpleLwM2MDevice simpleLwM2MDevice = new SimpleLwM2MDevice(executor); + initializer.setInstancesForObject(DEVICE, lwM2MDevice = simpleLwM2MDevice); initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice()); @@ -218,6 +225,7 @@ public class LwM2MTestClient { clientDtlsCid = new HashMap<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); + simpleLwM2MDevice.setLwM2MTestClient(this); LwM2mClientObserver observer = new LwM2mClientObserver() { @Override @@ -248,7 +256,7 @@ public class LwM2MTestClient { @Override public void onRegistrationSuccess(LwM2mServer server, RegisterRequest request, String registrationID) { clientStates.add(ON_REGISTRATION_SUCCESS); - } + } @Override public void onRegistrationFailure(LwM2mServer server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { @@ -268,6 +276,8 @@ public class LwM2MTestClient { @Override public void onUpdateSuccess(LwM2mServer server, UpdateRequest request) { clientStates.add(ON_UPDATE_SUCCESS); + countUpdateRegistrationSuccess++; + countReadObserveAfterUpdateRegistrationSuccess = 0; } @Override @@ -319,6 +329,12 @@ public class LwM2MTestClient { public void objectAdded(LwM2mObjectEnabler object) { log.info("Object {} v{} enabled.", object.getId(), object.getObjectModel().version); } + + @Override + public void resourceChanged(LwM2mPath... paths) { + countReadObserveAfterUpdateRegistrationSuccess++; + log.trace("resourceChanged paths {} cntReadObserve {} cntUpdateSuccess {} .", paths, countReadObserveAfterUpdateRegistrationSuccess, countUpdateRegistrationSuccess); + } }); leshanClient.start(); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mBinaryAppDataContainer.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mBinaryAppDataContainer.java new file mode 100644 index 0000000000..d8f598fa93 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -0,0 +1,230 @@ +/** + * Copyright © 2016-2024 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.msa.connectivity.lwm2m.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteResponse; + +import javax.security.auth.Destroyable; +import java.sql.Time; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + + +@Slf4j +public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements Destroyable { + + /** + * id = 0 + * Multiple + * base64 + */ + + /** + * Example1: + * InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFka + * W5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo= + * "serviceId":"Meter", + * "serviceData":{ + * "currentReading":"46.3", + * "signalStrength":16, + * "dailyActivityTime":5706 + */ + + /** + * Example2: + * InNlcnZpY2VJZCI6IldhdGVyTWV0ZXIiLA0KImNtZCI6IlNFVF9URU1QRVJBVFVSRV9SRUFEX + * 1BFUklPRCIsDQoicGFyYXMiOnsNCiJ2YWx1ZSI6NA0KICAgIH0sDQoNCg0K + * "serviceId":"WaterMeter", + * "cmd":"SET_TEMPERATURE_READ_PERIOD", + * "paras":{ + * "value":4 + * }, + */ + + Map data; + private Integer priority = 0; + private Time timestamp; + private String description; + private String dataFormat; + private Integer appID = -1; + private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 4, 5); + + public LwM2mBinaryAppDataContainer() { + } + + public LwM2mBinaryAppDataContainer(ScheduledExecutorService executorService, Integer id) { + try { + if (id != null) this.setId(id); + executorService.scheduleWithFixedDelay(() -> { + fireResourceChange(0); + fireResourceChange(2); + } + , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } + + @Override + public ReadResponse read(LwM2mServer identity, int resourceId) { + try { + switch (resourceId) { + case 0: + ReadResponse response = ReadResponse.success(resourceId, getData(), ResourceModel.Type.OPAQUE); + return response; + case 1: + return ReadResponse.success(resourceId, getPriority()); + case 2: + return ReadResponse.success(resourceId, getTimestamp()); + case 3: + return ReadResponse.success(resourceId, getDescription()); + case 4: + return ReadResponse.success(resourceId, getDataFormat()); + case 5: + return ReadResponse.success(resourceId, getAppID()); + default: + return super.read(identity, resourceId); + } + } catch (Exception e) { + return ReadResponse.badRequest(e.getMessage()); + } + } + + @Override + public WriteResponse write(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { + log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 0: + if (setData(value, replace)) { + return WriteResponse.success(); + } else { + WriteResponse.badRequest("Invalidate value ..."); + } + case 1: + setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue())); + fireResourceChange(resourceId); + return WriteResponse.success(); + case 2: + setTimestamp(((Date) value.getValue()).getTime()); + fireResourceChange(resourceId); + return WriteResponse.success(); + case 3: + setDescription((String) value.getValue()); + fireResourceChange(resourceId); + return WriteResponse.success(); + case 4: + setDataFormat((String) value.getValue()); + fireResourceChange(resourceId); + return WriteResponse.success(); + case 5: + setAppID((Integer) value.getValue()); + fireResourceChange(resourceId); + return WriteResponse.success(); + default: + return super.write(identity, replace, resourceId, value); + } + } + + private Integer getAppID() { + return this.appID; + } + + private void setAppID(Integer appId) { + this.appID = appId; + } + + private void setDataFormat(String value) { + this.dataFormat = value; + } + + private String getDataFormat() { + return this.dataFormat == null ? "OPAQUE" : this.dataFormat; + } + + private void setDescription(String value) { + this.description = value; + } + + private String getDescription() { +// return this.description == null ? "meter reading" : this.description; + return this.description; + } + + private void setTimestamp(long time) { + this.timestamp = new Time(time); + } + + private Time getTimestamp() { + return this.timestamp != null ? this.timestamp : new Time(new Date().getTime()); + } + + private boolean setData(LwM2mResource value, boolean replace) { + try { + if (value instanceof LwM2mMultipleResource) { + if (replace || this.data == null) { + this.data = new HashMap<>(); + } + value.getInstances().values().forEach(v -> { + this.data.put(v.getId(), (byte[]) v.getValue()); + }); + return true; + } else { + return false; + } + } catch (Exception e) { + return false; + } + } + + private Map getData() { + if (data == null) { + this.data = new HashMap<>(); + this.data.put(0, new byte[]{(byte) 0xAC}); + } + return data; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + } + + private int getPriority() { + return this.priority; + } + + private void setPriority(int value) { + this.priority = value; + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mTemperatureSensor.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mTemperatureSensor.java new file mode 100644 index 0000000000..2ad6fdc8d1 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mTemperatureSensor.java @@ -0,0 +1,190 @@ +/** + * Copyright © 2016-2024 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.msa.connectivity.lwm2m.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.LeshanClient; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.send.ManualDataSender; +import org.eclipse.leshan.client.servers.LwM2mServer; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.argument.Arguments; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ReadResponse; + +import javax.security.auth.Destroyable; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable { + + private static final String UNIT_CELSIUS = "cel"; + private double currentTemp = 20d; + private double minMeasuredValue = currentTemp; + private double maxMeasuredValue = currentTemp; + + private LeshanClient leshanClient; + private List containingValues; + protected static final Random RANDOM = new Random(); + private static final List supportedResources = Arrays.asList(5601, 5602, 5700, 5701); + + public LwM2mTemperatureSensor() { + + } + + public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { + try { + if (id != null) this.setId(id); + executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS); + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } + + @Override + public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { + log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 5601: + return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); + case 5602: + return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); + case 5700: + if (identity == LwM2mServer.SYSTEM) { + setTemperature(); + setData(); + return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + } else if (this.getId() == 12 && this.leshanClient != null) { + containingValues = new ArrayList<>(); + sendCollected(5700); + return ReadResponse.success(resourceId, getData()); + } else { + return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + } + case 5701: + return ReadResponse.success(resourceId, UNIT_CELSIUS); + default: + return super.read(identity, resourceId); + } + } + + @Override + public synchronized ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) { + log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 5605: + resetMinMaxMeasuredValues(); + return ExecuteResponse.success(); + default: + return super.execute(identity, resourceId, arguments); + } + } + + private double getTwoDigitValue(double value) { + BigDecimal toBeTruncated = BigDecimal.valueOf(value); + return toBeTruncated.setScale(2, RoundingMode.HALF_UP).doubleValue(); + } + + private void adjustTemperature() { + setTemperature(); + Integer changedResource = adjustMinMaxMeasuredValue(currentTemp); + fireResourceChange(5700); + if (changedResource != null) { + fireResourceChange(changedResource); + } + } + + private void setTemperature(){ + float delta = (RANDOM.nextInt(20) - 10) / 10f; + currentTemp += delta; + } + private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) { + if (newTemperature > maxMeasuredValue) { + maxMeasuredValue = newTemperature; + return 5602; + } else if (newTemperature < minMeasuredValue) { + minMeasuredValue = newTemperature; + return 5601; + } else { + return null; + } + } + + private void resetMinMaxMeasuredValues() { + minMeasuredValue = currentTemp; + maxMeasuredValue = currentTemp; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + protected void setLeshanClient(LeshanClient leshanClient){ + this.leshanClient = leshanClient; + } + + @Override + public void destroy() { + } + + private void sendCollected(int resourceId) { + try { + LwM2mServer registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); + ManualDataSender sender = this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME, + ManualDataSender.class); + sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); + Thread.sleep(1000); + sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); + sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + private LwM2mPath getPathForCollectedValue(int resourceId) { + return new LwM2mPath(3303, this.getId(), resourceId); + } + + private double getData() { + if (containingValues.size() > 1) { + Integer t0 = Math.toIntExact(Math.round(containingValues.get(0) * 100)); + Integer t1 = Math.toIntExact(Math.round(containingValues.get(1) * 100)); + long to_t1 = (((long) t0) << 32) | (t1 & 0xffffffffL); + return Double.longBitsToDouble(to_t1); + } else { + return currentTemp; + } + + } + + private void setData() { + if (containingValues == null){ + containingValues = new ArrayList<>(); + } + containingValues.add(getTwoDigitValue(currentTemp)); + } +} + diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mValueConverterImpl.java similarity index 99% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mValueConverterImpl.java index b49d54f27e..bb76569685 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mValueConverterImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel.Type; diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/Lwm2mTestHelper.java similarity index 90% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/Lwm2mTestHelper.java index dfb4c71a1e..059876e94b 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/Lwm2mTestHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.client.object.Security; @@ -44,16 +44,22 @@ public class Lwm2mTestHelper { public static final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID"; public static final String CLIENT_PSK_KEY = "73656372657450534b73656372657450"; + public static String OBSERVE_ATTRIBUTES_WITH_PARAMS = - public static final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS = " {\n" + - " \"keyName\": {},\n" + - " \"observe\": [],\n" + - " \"attribute\": [],\n" + - " \"telemetry\": [],\n" + + " \"keyName\": {\n" + + " \"/3_1.2/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [\n" + + " \"/3_1.2/0/9\"\n" + + " ],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.2/0/9\"\n" + + " ],\n" + " \"attributeLwm2m\": {}\n" + " }"; - public static final String CLIENT_LWM2M_SETTINGS = " {\n" + " \"edrxCycle\": null,\n" + diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/SimpleLwM2MDevice.java similarity index 79% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/SimpleLwM2MDevice.java index d2aa1af4d2..eefeb4bdf8 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/SimpleLwM2MDevice.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.servers.LwM2mServer; @@ -36,8 +37,11 @@ import java.util.Map; import java.util.PrimitiveIterator; import java.util.Random; import java.util.TimeZone; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; @Slf4j +@Data public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable { @@ -46,17 +50,30 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl private static final int max = 50; private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator(); private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); - + private int countReadObserveAfterUpdateRegistrationSuccess_3_0_9; + private int countUpdateRegistrationSuccessLast; + private LwM2MTestClient lwM2MTestClient; public SimpleLwM2MDevice() { } - + public SimpleLwM2MDevice(ScheduledExecutorService executorService) { + try { + executorService.scheduleWithFixedDelay(() -> { + fireResourceChange(9); + } + , 1, 1, TimeUnit.SECONDS); // 30 MIN +// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } @Override public ReadResponse read(LwM2mServer identity, int resourceId) { if (!identity.isSystem()) - log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); + log.trace("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { case 0: return ReadResponse.success(resourceId, getManufacturer()); @@ -155,8 +172,15 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } private int getBatteryLevel() { + int batteryLevel = randomIterator.nextInt(); + if (countUpdateRegistrationSuccessLast != this.lwM2MTestClient.getCountUpdateRegistrationSuccess()) { + countUpdateRegistrationSuccessLast = this.lwM2MTestClient.getCountUpdateRegistrationSuccess(); + countReadObserveAfterUpdateRegistrationSuccess_3_0_9 = 0; + } + countReadObserveAfterUpdateRegistrationSuccess_3_0_9++; + this.lwM2MTestClient.setCountReadObserveAfterUpdateRegistrationSuccess(countReadObserveAfterUpdateRegistrationSuccess_3_0_9); + log.info("Read on Device resource /{}/{}/9 batteryLevel = {}, cntReadAfterUpdateReg [{}] ", getModel().id, getId(), batteryLevel, countReadObserveAfterUpdateRegistrationSuccess_3_0_9); return randomIterator.nextInt(); -// return 42; } private long getMemoryFree() { @@ -212,6 +236,10 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl return supportedResources; } + protected void setLwM2MTestClient(LwM2MTestClient lwM2MTestClient){ + this.lwM2MTestClient = lwM2MTestClient; + } + @Override public void destroy() { } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveCompositeTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveCompositeTest.java new file mode 100644 index 0000000000..3eab4560fc --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveCompositeTest.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2024 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.msa.connectivity.lwm2m.rpc; + +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.thingsboard.server.msa.DisableUIListeners; +import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; +import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; + +import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; +import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; + +@DisableUIListeners +public class Lwm2mObserveCompositeTest extends AbstractLwm2mClientTest { + + private Lwm2mDevicesForTest lwm2mDevicesForTest; + + private final static String name = "lwm2m-NoSec-ObserveComposite"; + + @BeforeMethod + public void setUp() throws Exception { + testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); + this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest(name + "-profile" + RandomStringUtils.randomAlphanumeric(7))); + } + + @AfterMethod + public void tearDown() { + destroyAfter(this.lwm2mDevicesForTest); + } + + @Test + public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { + createLwm2mDevicesForConnectNoSec( name + "-" + RandomStringUtils.randomAlphanumeric(7), this.lwm2mDevicesForTest ); + observeCompositeResource_Update_AfterUpdateRegistration_test(this.lwm2mDevicesForTest.getLwM2MTestClient(), this.lwm2mDevicesForTest.getLwM2MDeviceTest().getId().toString()); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveTest.java new file mode 100644 index 0000000000..1e10a692c8 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveTest.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2024 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.msa.connectivity.lwm2m.rpc; + +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; +import org.thingsboard.server.msa.DisableUIListeners; +import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; + +import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; +import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; + +@DisableUIListeners +public class Lwm2mObserveTest extends AbstractLwm2mClientTest { + + private Lwm2mDevicesForTest lwm2mDevicesForTest; + + private final static String name = "lwm2m-NoSec-Observe"; + + @BeforeMethod + public void setUp() throws Exception { + testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); + this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest(name + "-profile" + RandomStringUtils.randomAlphanumeric(7))); + } + + @AfterMethod + public void tearDown() { + destroyAfter(this.lwm2mDevicesForTest); + } + + @Test + public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { + createLwm2mDevicesForConnectNoSec( name + "-" + RandomStringUtils.randomAlphanumeric(7), this.lwm2mDevicesForTest ); + observeResource_Update_AfterUpdateRegistration_test(this.lwm2mDevicesForTest.getLwM2MTestClient(), this.lwm2mDevicesForTest.getLwM2MDeviceTest().getId().toString()); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientNoSecTest.java similarity index 60% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientNoSecTest.java index 15658140b0..c614dedf2f 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientNoSecTest.java @@ -13,33 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity; +package org.thingsboard.server.msa.connectivity.lwm2m.security; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import org.thingsboard.server.msa.AbstractLwm2mClientTest; +import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; import org.thingsboard.server.msa.DisableUIListeners; +import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_ENDPOINT_NO_SEC; import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; @DisableUIListeners public class Lwm2mClientNoSecTest extends AbstractLwm2mClientTest { + private Lwm2mDevicesForTest lwm2mDevicesForTest; @BeforeMethod public void setUp() throws Exception { testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); - initTest("lwm2m-NoSec"); + this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest("lwm2m-NoSec-profile" + "-" + RandomStringUtils.randomAlphanumeric(7))); } @AfterMethod public void tearDown() { - destroyAfter(); + destroyAfter(this.lwm2mDevicesForTest); } @Test public void connectLwm2mClientNoSecWithLwm2mServer() throws Exception { - connectLwm2mClientNoSec(); + createLwm2mDevicesForConnectNoSec(CLIENT_ENDPOINT_NO_SEC, this.lwm2mDevicesForTest); + basicTestConnection(this.lwm2mDevicesForTest.getLwM2MTestClient(), "TestConnection Lwm2m NoSec (msa)"); } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientPskTest.java similarity index 64% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientPskTest.java index fbd8139d8f..9eab67f999 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientPskTest.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity; +package org.thingsboard.server.msa.connectivity.lwm2m.security; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import org.thingsboard.server.msa.AbstractLwm2mClientTest; +import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; import org.thingsboard.server.msa.DisableUIListeners; +import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; @@ -27,19 +29,21 @@ import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; @DisableUIListeners public class Lwm2mClientPskTest extends AbstractLwm2mClientTest { + private Lwm2mDevicesForTest lwm2mDevicesForTest; @BeforeMethod public void setUp() throws Exception { testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); - initTest("lwm2m-Psk"); + this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest("lwm2m-Psk-profile" + "-" + RandomStringUtils.randomAlphanumeric(7))); } @AfterMethod public void tearDown() { - destroyAfter(); + destroyAfter(this.lwm2mDevicesForTest); } @Test public void connectLwm2mClientPskWithLwm2mServer() throws Exception { - connectLwm2mClientPsk(); + createLwm2mDevicesForConnectPsk(this.lwm2mDevicesForTest); + basicTestConnection(this.lwm2mDevicesForTest.getLwM2MTestClient(), "TestConnection Lwm2m Rpc (msa)"); } } diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/19.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/19.xml new file mode 100644 index 0000000000..ffa21864b7 --- /dev/null +++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/19.xml @@ -0,0 +1,144 @@ + + + + + BinaryAppDataContainer + + 19 + urn:oma:lwm2m:oma:19:1.1 + 1.1 + 1.1 + Multiple + Optional + + Data + RW + Multiple + Mandatory + Opaque + + + + + Data Priority + RW + Single + Optional + Integer + 1 bytes + + + + Data Creation Time + RW + Single + Optional + Time + + + + + Data Description + RW + Single + Optional + String + 32 bytes + + + + Data Format + RW + Single + Optional + String + 32 bytes + + + + App ID + RW + Single + Optional + Integer + 2 bytes + + + + + + diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/3303.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/3303.xml new file mode 100644 index 0000000000..a6e5406b08 --- /dev/null +++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/3303.xml @@ -0,0 +1,103 @@ + + + + + Temperature + This IPSO object should be used with a temperature sensor to report a temperature measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the temperature sensor. An example measurement unit is degrees Celsius. + 3303 + urn:oma:lwm2m:ext:3303 + 1.0 + 1.0 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value + + + + +