Browse Source

lwm2m: fix bug Observe Composite

pull/11597/head
nick 2 years ago
parent
commit
2bd18ee5dd
  1. 7
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  2. 144
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java
  3. 20
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  4. 236
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  5. 160
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java
  6. 135
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java
  7. 16
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  8. 36
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java
  9. 11
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  10. 241
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java
  11. 20
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mDevicesForTest.java
  12. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/FwLwM2MDevice.java
  13. 66
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java
  14. 230
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mBinaryAppDataContainer.java
  15. 190
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mTemperatureSensor.java
  16. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mValueConverterImpl.java
  17. 20
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/Lwm2mTestHelper.java
  18. 38
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/SimpleLwM2MDevice.java
  19. 52
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveCompositeTest.java
  20. 52
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveTest.java
  21. 15
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientNoSecTest.java
  22. 14
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientPskTest.java
  23. 144
      msa/black-box-tests/src/test/resources/lwm2m-registry/19.xml
  24. 103
      msa/black-box-tests/src/test/resources/lwm2m-registry/3303.xml

7
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 =

144
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());
}
}

20
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;
}
}
}

236
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);
}
}

160
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<Observation> 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<String, String> 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<Observation> removed) {
// Absorption by existing Observations
private void updateObservation(String registrationId, Observation observation, boolean addIfAbsent, List<Observation> 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<ObservationIdentifier>());
}
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<SingleObservation> 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<String, String> ctx, String serializedObservation, TokenGenerator tokenGenerator) {
Token token = tokenGenerator.createToken(Scope.SHORT_TERM);
Map<String, String> 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<Observation> removed) {
for (Observation obs : unsafeGetObservations(registrationId)) {
cancelExistingObservation(observation, obs, removed);
}
}
private void cancelExistingObservation(Observation observation, Observation obs, List<Observation> 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

135
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<String, String> 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<Observation> removed, RedisConnection connection) {
private void updateObservation(String registrationId, Observation observation, boolean addIfAbsent,
List<Observation> 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<Observation> 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<SingleObservation> result = new AtomicReference<>();
Collection<Observation> 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<Observation> 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<Observation> 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);
}
}
}

16
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());

36
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<Integer, Object> 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<Integer, Object> convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) {
Map<Integer, Object> 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);

11
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}")

241
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java → 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<LwM2MClientState> 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<LwM2MBootstrapServerCredential> 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);
}
}

20
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;
}
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java → 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;

66
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java → 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<LwM2MClientState, Integer> 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();

230
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<Integer, byte[]> data;
private Integer priority = 0;
private Time timestamp;
private String description;
private String dataFormat;
private Integer appID = -1;
private static final List<Integer> 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<Integer, byte[]> getData() {
if (data == null) {
this.data = new HashMap<>();
this.data.put(0, new byte[]{(byte) 0xAC});
}
return data;
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
@Override
public void destroy() {
}
private int getPriority() {
return this.priority;
}
private void setPriority(int value) {
this.priority = value;
}
}

190
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<Double> containingValues;
protected static final Random RANDOM = new Random();
private static final List<Integer> 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<Integer> 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));
}
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java → 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;

20
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java → 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" +

38
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java → 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<Integer> 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() {
}

52
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());
}
}

52
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());
}
}

15
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java → 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)");
}
}

14
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java → 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)");
}
}

144
msa/black-box-tests/src/test/resources/lwm2m-registry/19.xml

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
FILE INFORMATION
OMA Permanent Document
File: OMA-SUP-LwM2M_BinaryAppDataContainer-V1_0_1-20190221-A
Type: xml
Public Reachable Information
Path: http://www.openmobilealliance.org/tech/profiles
Name: LwM2M_BinaryAppDataContainer-v1_0_1.xml
NORMATIVE INFORMATION
Information about this file can be found in the latest revision of
OMA-TS-LWM2M_BinaryAppDataContainer-V1_0_1
This is available at http://www.openmobilealliance.org/
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues
CHANGE HISTORY
15062018 Status changed to Approved by DM, Doc Ref # OMA-DM&SE-2018-0061-INP_LWM2M_APPDATA_V1_0_ERP_for_final_Approval
21022019 Status changed to Approved by IPSO, Doc Ref # OMA-IPSO-2019-0025-INP_LwM2M_Object_App_Data_Container_1_0_1_for_Final_Approval
LEGAL DISCLAIMER
Copyright 2019 Open Mobile Alliance.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
The above license is used as a license under copyright only. Please
reference the OMA IPR Policy for patent licensing terms:
https://www.omaspecworks.org/about/intellectual-property-rights/
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd">
<Object ObjectType="MODefinition">
<Name>BinaryAppDataContainer</Name>
<Description1><![CDATA[This LwM2M Objects provides the application service data related to a LwM2M Server, eg. Water meter data.
There are several methods to create instance to indicate the message direction based on the negotiation between Application and LwM2M. The Client and Server should negotiate the instance(s) used to exchange the data. For example:
- Using a single instance for both directions communication, from Client to Server and from Server to Client.
- Using an instance for communication from Client to Server and another one for communication from Server to Client
- Using several instances
]]></Description1>
<ObjectID>19</ObjectID>
<ObjectURN>urn:oma:lwm2m:oma:19:1.1</ObjectURN>
<LWM2MVersion>1.1</LWM2MVersion>
<ObjectVersion>1.1</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0"><Name>Data</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Opaque</Type>
<RangeEnumeration />
<Units />
<Description><![CDATA[Indicates the application data content.]]></Description>
</Item>
<Item ID="1"><Name>Data Priority</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>1 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the Application data priority:
0:Immediate
1:BestEffort
2:Latest
3-100: Reserved for future use.
101-254: Proprietary mode.]]></Description>
</Item>
<Item ID="2"><Name>Data Creation Time</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Time</Type>
<RangeEnumeration />
<Units />
<Description><![CDATA[Indicates the Data instance creation timestamp.]]></Description>
</Item>
<Item ID="3"><Name>Data Description</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration>32 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the data description.
e.g. "meter reading".]]></Description>
</Item>
<Item ID="4"><Name>Data Format</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration>32 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the format of the Application Data.
e.g. YG-Meter-Water-Reading
UTF8-string
]]></Description>
</Item>
<Item ID="5"><Name>App ID</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Integer</Type>
<RangeEnumeration>2 bytes</RangeEnumeration>
<Units />
<Description><![CDATA[Indicates the destination Application ID.]]></Description>
</Item></Resources>
<Description2><![CDATA[]]></Description2>
</Object>
</LWM2M>

103
msa/black-box-tests/src/test/resources/lwm2m-registry/3303.xml

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright © 2016-2018 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.
-->
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd">
<Object ObjectType="MODefinition">
<Name>Temperature</Name>
<Description1>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.</Description1>
<ObjectID>3303</ObjectID>
<ObjectURN>urn:oma:lwm2m:ext:3303</ObjectURN>
<LWM2MVersion>1.0</LWM2MVersion>
<ObjectVersion>1.0</ObjectVersion>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="5700">
<Name>Sensor Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Last or Current Measured Value from the Sensor</Description>
</Item>
<Item ID="5601">
<Name>Min Measured Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The minimum value measured by the sensor since power ON or reset</Description>
</Item>
<Item ID="5602">
<Name>Max Measured Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The maximum value measured by the sensor since power ON or reset</Description>
</Item>
<Item ID="5603">
<Name>Min Range Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The minimum value that can be measured by the sensor</Description>
</Item>
<Item ID="5604">
<Name>Max Range Value</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>Float</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>The maximum value that can be measured by the sensor</Description>
</Item>
<Item ID="5701">
<Name>Sensor Units</Name>
<Operations>R</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Measurement Units Definition.</Description>
</Item>
<Item ID="5605">
<Name>Reset Min and Max Measured Values</Name>
<Operations>E</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Type></Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description>Reset the Min and Max Measured Values to Current Value</Description>
</Item>
</Resources>
<Description2></Description2>
</Object>
</LWM2M>
Loading…
Cancel
Save