From 69afda634b25f10cb092104138859eb158f5c0e1 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Fri, 23 Jan 2026 15:22:33 +0200 Subject: [PATCH] lwm2m - execute with params --- .../lwm2m/client/SimpleLwM2MDevice.java | 9 +++- .../sql/RpcLwm2mIntegrationExecuteTest.java | 51 +++++++++++++++++-- .../DefaultLwM2mDownlinkMsgHandler.java | 12 +++-- .../rpc/DefaultLwM2MRpcRequestHandler.java | 6 +++ .../lwm2m/server/rpc/RpcExecuteRequest.java | 27 ++++++++++ 5 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcExecuteRequest.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index 191bc97579..fb94fa2a91 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -160,7 +160,14 @@ 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: + return ExecuteResponse.success(); + case 5: + return ExecuteResponse.success(); + default: + return super.execute(identity, resourceId, arguments); + } } @Override diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java index f5b4538c1c..6de02d6a5e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java +++ b/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,14 +91,54 @@ 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 testExecuteResourceWithParametersOnlyOneDigitValueNullById_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 testExecuteResourceWithParametersDigit2Value60ById_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 after connect with link on device) + * Execute {"id":"3/0/5","value":"2,0='https://thingsboard.io/docs/reference/lwm2m-api/'"} + */ + @Test + public void testExecuteResourceWithParametersDigit2_0_ValueLinkById_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 after connect with link on device) + * Execute {"id":"3/0/5","value":"0,1,2,3,4,5,6,7,8,9"} + */ + @Test + public void testExecuteResourceWithParametersDigitManyValueNullById_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()); @@ -178,7 +219,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT } private String sendRPCExecuteWithValueById(String path, Object value) throws Exception { - String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; + String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\", \"value\": \"" + value + "\"}}"; return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 8f638cfa2a..9576ab7228 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -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; @@ -281,8 +284,11 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } 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()))); + Object params = request.getParams(); + ResourceModel.Type resourceModel = resourceModelExecute.type == ResourceModel.Type.NONE ? equalsResourceTypeGetSimpleName(params) : resourceModelExecute.type; + String args = (String) this.converter.convertValue(params, resourceModel, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId())); + Arguments arguments = Arguments.parse(args); + downlink = new ExecuteRequest(request.getObjectId(), arguments); } else { downlink = new ExecuteRequest(request.getObjectId()); } @@ -290,7 +296,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } else { callback.onValidationError(request.toString(), "Resource with " + request.getVersionedId() + " is not executable."); } - } catch (InvalidRequestException e) { + } catch (InvalidRequestException | InvalidArgumentException e) { callback.onValidationError(request.toString(), e.getMessage()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 6980fb6341..e3bf6e8daa 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -241,7 +241,13 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } private void sendExecuteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + RpcExecuteRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcExecuteRequest.class); TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId).timeout(clientContext.getRequestTimeout(client)).build(); + if (!requestMsg.getParams().isEmpty()) { + downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId) + .params(requestBody.getValue()) + .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); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcExecuteRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcExecuteRequest.java new file mode 100644 index 0000000000..e0360c6f71 --- /dev/null +++ b/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; + +}