Browse Source

Merge branch 'master' into master

pull/15609/head
Samarth Tikotkar 2 weeks ago
committed by GitHub
parent
commit
62478c1daf
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  2. 114
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java
  3. 44
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  4. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  5. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcExecuteRequest.java

8
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -160,7 +160,13 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
if (!arguments.isEmpty())
withArguments = " with arguments " + arguments;
log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withArguments);
return ExecuteResponse.success();
switch (resourceId) {
case 4:
case 5:
return ExecuteResponse.success();
default:
return super.execute(identity, resourceId, arguments);
}
}
@Override

114
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java

@ -29,6 +29,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_4;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_5;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_8;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
@ -90,26 +91,101 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
/**
* execute_resource_with_parameters (execute reboot after 60 seconds on device)
* Execute {"id":"3/0/4","value":60}
* execute_resource_with_parameters (execute reboot if digit = 5 on device)
* Execute {"id":"3/0/4","value":5}
* {"result":"CHANGED"}
*/
@Test
public void testExecuteResourceWithParametersById_Result_CHANGED() throws Exception {
public void testExecuteResourceWithParametersSingleDigitValueById_Result_Ok() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_4;
Object expectedValue = 60;
Object expectedValue = 5;
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* execute_resource_with_parameters (execute Factory Reset if digit = 2 -> after 60 seconds on device)
* Execute {"id":"3/0/5","value":"2='60'"}
*/
@Test
public void testExecuteResourceWithParametersArgumentIdAndValueById_Result_Ok() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_5;
Object expectedValue = "2='60'";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* execute_resource_with_parameters (execute Factory Reset with two arguments:
* digit 2 without a value and digit 0 with the link value on device)
* Execute {"id":"3/0/5","value":"2,0='https://thingsboard.io/docs/reference/lwm2m-api/'"}
*/
@Test
public void testExecuteResourceWithParametersMultipleArgumentsIncludingLinkById_Result_Ok() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_5;
Object expectedValue = "2,0='https://thingsboard.io/docs/reference/lwm2m-api/'";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* execute_resource_with_parameters (execute Factory Reset with multiple arguments without values)
* According to the OMA LwM2M execute arguments format, this represents ten arguments (digits 0-9), all without values.
* Execute {"id":"3/0/5","value":"0,1,2,3,4,5,6,7,8,9"}
*/
@Test
public void testExecuteResourceWithParametersMultipleArgumentsById_Result_Ok() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_5;
Object expectedValue = "0,1,2,3,4,5,6,7,8,9";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
}
/**
* execute_resource_with_parameters (execute Factory Reset after 60 seconds on device)
* Execute {"id":"3/0/5","value":"'60'"}
*/
@Test
public void testExecuteResourceWithParametersSingleDigitValueInvalidById_Result_BAD_REQUEST_Error_IntegerBetween_0_And_9_Expected() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_5;
Object expectedValue = "'60'";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Unable to parse Arguments [" + expectedValue + "] : Invalid digit ['] (an integer between 0 and 9 is expected)";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.contains(expected));
}
/**
* execute_resource_with_parameters (execute Bad with Unable to parse Arguments)
* Execute {"id":"3/0/5","value":"0,1,2,3,4,5,6,7,8,9,60"}
*/
@Test
public void testExecuteResourceWithParametersMultipleArgumentsById_Result_BAD_REQUEST_Error_UnableParseArguments() throws Exception {
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_5;
Object expectedValue = "0,1,2,3,4,5,6,7,8,9,60";
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue);;
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Unable to parse Arguments [" + expectedValue + "] : [,] separator expected at index 21 after [0,1,2,3,4,5,6,7,8,9,6]";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.contains(expected));
}
/**
* Bootstrap-Request Trigger
* Execute {"id":"1/0/9"}
* {"result":"BAD_REQUEST","error":"probably no bootstrap server configured"}
*/
@Test
public void testExecuteBootstrapRequestTriggerById_Result_BAD_REQUEST_Error_NoBootstrapServerConfigured() throws Exception {
public void testExecuteBootstrapRequestTriggerById_Result_BAD_REQUEST_Error_NoBootstrapServer() throws Exception {
String expectedPath = objectInstanceIdVer_1 + "/" + RESOURCE_ID_9;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -125,7 +201,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
* {"result":"BAD_REQUEST","error":"Resource with /5_1.0/0/3 is not executable."}
*/
@Test
public void testExecuteResourceWithOperationNotExecuteById_Result_METHOD_NOT_ALLOWED() throws Exception {
public void testExecuteResourceWithOperationNotExecuteById_Result_BAD_REQUEST_Error_Is_Not_Executable() throws Exception {
String expectedPath = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -141,7 +217,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
* {"result":"BAD_REQUEST","error":"Specified object id 50 absent in the list supported objects of the client or is security object!"}
*/
@Test
public void testExecuteNonExistingResourceOnNonExistingObjectById_Result_BAD_REQUEST() throws Exception {
public void testExecuteNonExistingResourceOnNonExistingObjectById_Result_BAD_REQUEST_Error_Specified_Object_Absent_List_Supported() throws Exception {
String expectedPath = OBJECT_ID_VER_50 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -159,7 +235,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
* {"result":"BAD_REQUEST","error":"Specified object id 0 absent in the list supported objects of the client or is security object!"}
*/
@Test
public void testExecuteSecurityObjectById_Result_NOT_FOUND() throws Exception {
public void testExecuteSecurityObjectById_Result_BAD_REQUEST_Error_SpecifiedObjectAbsent() throws Exception {
String expectedPath = objectIdVer_0 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3;
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -178,8 +254,26 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
}
private String sendRPCExecuteWithValueById(String path, Object value) throws Exception {
String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}";
return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk());
ObjectNode params = JacksonUtil.newObjectNode();
params.put("id", path);
// Jackson сам вирішить: ставити лапки (рядок) чи ні (число/boolean/null)
if (value instanceof String) {
params.put("value", (String) value);
} else if (value instanceof Integer) {
params.put("value", (Integer) value);
} else if (value instanceof Boolean) {
params.put("value", (Boolean) value);
} else {
params.set("value", JacksonUtil.valueToTree(value));
}
ObjectNode setRpcRequest = JacksonUtil.newObjectNode();
setRpcRequest.put("method", "Execute");
setRpcRequest.set("params", params);
return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(),
JacksonUtil.toString(setRpcRequest), String.class, status().isOk());
}
}

44
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java

@ -50,6 +50,8 @@ import org.eclipse.leshan.core.request.SimpleDownlinkRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.argument.Arguments;
import org.eclipse.leshan.core.request.argument.InvalidArgumentException;
import org.eclipse.leshan.core.request.exception.ClientSleepingException;
import org.eclipse.leshan.core.request.exception.InvalidRequestException;
import org.eclipse.leshan.core.request.exception.TimeoutException;
@ -116,6 +118,7 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.thingsboard.server.common.transport.util.JsonUtils.isBase64;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertMultiResourceValuesFromRpcBody;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.createModelsDefault;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.equalsResourceTypeGetSimpleName;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.getVerFromPathIdVerOrId;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.validateVersionedId;
@ -268,29 +271,34 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
validateVersionedId(client, request);
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(request.getVersionedId()));
ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), modelProvider);
if (resourceModelExecute == null) {
LwM2mModel model = createModelsDefault();
if (pathIds.isResource()) {
resourceModelExecute = model.getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
}
if (resourceModelExecute == null && pathIds.isResource()) {
resourceModelExecute = createModelsDefault().getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
}
if (resourceModelExecute == null) {
callback.onValidationError(request.toString(), "ResourceModel with " + request.getVersionedId() +
" is absent in system. Need ddd Lwm2m Model with id=" + pathIds.getObjectId() + " ver=" +
getVerFromPathIdVerOrId(request.getVersionedId()) + " to profile.");
} else if (resourceModelExecute.operations.isExecutable()) {
ExecuteRequest downlink;
if (request.getParams() != null && !resourceModelExecute.multiple) {
downlink = new ExecuteRequest(request.getObjectId(), (String) this.converter.convertValue(request.getParams(),
resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId())));
} else {
downlink = new ExecuteRequest(request.getObjectId());
throw new InvalidArgumentException(String.format("ResourceModel with %s is absent in the system. Need to add Model with id= %s ver=%s to profile.",
request.getVersionedId(), pathIds.getObjectId(), getVerFromPathIdVerOrId(request.getVersionedId())));
}
if (!resourceModelExecute.operations.isExecutable()) {
throw new InvalidArgumentException(String.format("Resource with %s is not executable.", request.getVersionedId()));
}
ExecuteRequest downlink;
Object params = request.getParams();
// 4. Handle parameters if they exist and the resource is not a multiple-instance resource
if (params != null && !resourceModelExecute.multiple) {
ResourceModel.Type resourceModelType = equalsResourceTypeGetSimpleName(params);
if (resourceModelType == null) {
throw new InvalidArgumentException(String.format("Unsupported parameter type: %s. Only simple types (String, Integer, Boolean, etc.) are allowed for Execute arguments.",
params.getClass().getSimpleName()));
}
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
String args = (String) this.converter.convertValue(params, resourceModelType, ResourceModel.Type.STRING, pathIds);
downlink = new ExecuteRequest(request.getObjectId(), args);
} else {
callback.onValidationError(request.toString(), "Resource with " + request.getVersionedId() + " is not executable.");
downlink = new ExecuteRequest(request.getObjectId());
}
} catch (InvalidRequestException e) {
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} catch (Exception e) {
log.error("[{}] Validation failed for Execute request: {}", client.getEndpoint(), e.getMessage());
callback.onValidationError(request.toString(), e.getMessage());
}
}

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java

@ -241,7 +241,13 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
}
private void sendExecuteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) {
TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId).timeout(clientContext.getRequestTimeout(client)).build();
RpcExecuteRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcExecuteRequest.class);
Object value = requestBody != null ? requestBody.getValue() : null;
TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder()
.versionedId(versionedId)
.params(value)
.timeout(clientContext.getRequestTimeout(client))
.build();
var mainCallback = new TbLwM2MExecuteCallback(logService, client, versionedId);
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback);
downlinkHandler.sendExecuteRequest(client, downlink, rpcCallback);

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcExecuteRequest.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.rpc;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class RpcExecuteRequest extends LwM2MRpcRequestHeader {
private Object value;
}
Loading…
Cancel
Save