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 12bc176fe3..ed685dfc6c 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 @@ -113,6 +113,7 @@ import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.SHOR import static org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes.STEP; import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; 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.fromVersionedIdToObjectId; @@ -399,7 +400,8 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im try { Object valueForMultiResource = request.getValue(); if (resultIds.isResourceInstance()) { - String resourceInstance = "{" + resultIds.getResourceInstanceId() + "=" + request.getValue() + "}"; + String valueStr = isBase64(request.getValue().toString()) ? "\"" + request.getValue() + "\"" : request.getValue().toString(); + String resourceInstance = "{" + resultIds.getResourceInstanceId() + "=" + valueStr + "}"; valueForMultiResource = JsonParser.parseString(resourceInstance); } Map value = convertMultiResourceValuesFromRpcBody(valueForMultiResource, resourceModelWrite.type, request.getObjectId()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 2f2407e451..8ed9c69755 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -15,16 +15,21 @@ */ package org.thingsboard.server.transport.lwm2m.server.ota; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.hash.Hashing; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.request.ContentFormat; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; @@ -37,15 +42,12 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; +import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService; -import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.*; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareDeliveryMethod; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; @@ -56,6 +58,7 @@ import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwO import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; +import org.thingsboard.server.transport.lwm2m.server.rpc.RpcCreateRequest; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MClientOtaInfoStore; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; @@ -64,6 +67,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.Base64; import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; @@ -104,6 +108,14 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl public static final String FW_RESULT_ID = "/5/0/5"; public static final String FW_NAME_ID = "/5/0/6"; public static final String FW_VER_ID = "/5/0/7"; + public static final String FW_INFO_19_INSTANCE_ID = "/19/65534"; + public static final String SW_INFO_19_INSTANCE_ID = "/19/65535"; + public static final String OTA_INFO_19_TITLE = "title"; + public static final String OTA_INFO_19_VERSION = "version"; + public static final String OTA_INFO_19_FILE_CHECKSUM256 = "fileChecksumSHA256"; + public static final String OTA_INFO_19_FILE_SIZE = "fileSize"; + public static final String OTA_INFO_19_FILE_NAME = "fileName"; + /** * Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8 * Revision:BC68JAR01A10 @@ -134,6 +146,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl private final LwM2MTelemetryLogService logService; private final LwM2mTransportServerHelper helper; private final TbLwM2MClientOtaInfoStore otaInfoStore; + private final LwM2mVersionedModelProvider modelProvider; @Autowired @Lazy @@ -510,6 +523,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } else { strategy = info.getDeliveryMethod() == FirmwareDeliveryMethod.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; } + if (clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings().getUseObject19ForOta()){ + sendInfoFwToObject19ForOta(client, convertObjectIdToVersionedId(FW_INFO_19_INSTANCE_ID, client), response, otaPackageId); + } switch (strategy) { case OBJ_5_BINARY: startUpdateUsingBinary(client, convertObjectIdToVersionedId(FW_PACKAGE_5_ID, client), otaPackageId); @@ -641,6 +657,61 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl helper.sendParametersOnThingsboardTelemetry(result, client.getSession(), client.getKeyTsLatestMap()); } + /** + * send to client: versionedId="/19/65534/0/0, value = FwOtaInfo in bas64 -> format json: + * {"title":"BC68JAR01", + * "version":"A10", + * "fileChecksumSHA256":"f2a08d4963e981c78f2a99f62d8439af4437a72ea7267a8c01d013c072c01ded", + * "fileSize":59832} + * @param client + * @param targetIdVer + * @param response + * @param otaPackageId + */ + private void sendInfoFwToObject19ForOta(LwM2mClient client, String targetIdVer, TransportProtos.GetOtaPackageResponseMsg response, UUID otaPackageId) { + log.trace("[{}] Current info fw toObject19ForOta", client.getEndpoint()); + ObjectModel objectModel = client.getObjectModel(targetIdVer, modelProvider); + if (objectModel != null) { + try { + if (client.getRegistration().getSupportedObject().get(19) != null) { + ObjectNode objectNodeInfoFw = JacksonUtil.newObjectNode(); + byte[] firmwareChunk = otaPackageDataCache.get(otaPackageId.toString(), 0, 0); + String fileChecksumSHA256 = Hashing.sha256().hashBytes(firmwareChunk).toString(); + objectNodeInfoFw.put(OTA_INFO_19_TITLE, response.getTitle()); + objectNodeInfoFw.put(OTA_INFO_19_VERSION, response.getVersion()); + objectNodeInfoFw.put(OTA_INFO_19_FILE_CHECKSUM256, fileChecksumSHA256); + objectNodeInfoFw.put(OTA_INFO_19_FILE_SIZE, firmwareChunk.length); + objectNodeInfoFw.put(OTA_INFO_19_FILE_NAME, response.getFileName()); + String objectNodeInfoFwStr = JacksonUtil.toString(objectNodeInfoFw); + assert objectNodeInfoFwStr != null; + String objectNodeInfoFwBase64 = Base64.getEncoder().encodeToString(objectNodeInfoFwStr.getBytes()); + + + LwM2mPath pathFwInstance = new LwM2mPath(FW_INFO_19_INSTANCE_ID); + if (client.getRegistration().getAvailableInstances().contains(pathFwInstance)) { + String versionId = targetIdVer + "/0/0"; + TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionId).value(objectNodeInfoFwBase64).timeout(clientContext.getRequestTimeout(client)).build(); + downlinkHandler.sendWriteReplaceRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionId)); + } else { + String valueResourcesStr = "{\"" + 0 + "\":{\"0\":\"" + objectNodeInfoFwBase64 + "\"}}"; + String valueStr = "{\"id\":\"" + targetIdVer + "\",\"value\":" + valueResourcesStr + "}"; + RpcCreateRequest requestBody = JacksonUtil.fromString(valueStr, RpcCreateRequest.class); + assert requestBody != null; + TbLwM2MCreateRequest.TbLwM2MCreateRequestBuilder builder = TbLwM2MCreateRequest.builder().versionedId(targetIdVer); + builder.value(requestBody.getValue()).nodes(requestBody.getNodes()).timeout(clientContext.getRequestTimeout(client)); + downlinkHandler.sendCreateRequest(client, builder.build(), new TbLwM2MCreateResponseCallback(uplinkHandler, logService, client, targetIdVer)); + } + } else { + String errorMsg = "Failed to send Info Fw to object 19. The client does not have object 19."; + log.trace("[{}] {}", client.getEndpoint(), errorMsg); + logService.log(client, errorMsg); + } + } catch (Exception e){ + log.error("", e); + } + } + } + private static Optional toOtaPackageUpdateStatus(FirmwareUpdateResult fwUpdateResult) { switch (fwUpdateResult) { case INITIAL: diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index b5ec94d48b..d1a8052610 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -74,18 +74,16 @@ public abstract class LwM2MClientOtaInfo { } else { if (targetPackageId.equals(currentPackageId)) { return false; - } else if (StringUtils.isNotEmpty(targetTag) && targetTag.equals(currentPackageId)) { - return false; - } else if (StringUtils.isNotEmpty(currentVersion3)) { - if (StringUtils.isNotEmpty(targetTag) && currentVersion3.contains(targetTag)) { + } else{ + if (targetTag.equals(currentPackageId)) { return false; + } else if (StringUtils.isNotEmpty(currentVersion3)) { + return !(currentVersion3.contains(targetTag) || targetTag.contains(currentVersion3)); } - return !currentVersion3.contains(targetPackageId); - } else { - return true; } } } + return true; } @JsonIgnore diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java index 28a904edb9..a79d897daf 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/JsonUtils.java @@ -23,9 +23,13 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; public class JsonUtils { + private static final Pattern BASE64_PATTERN = + Pattern.compile("^[A-Za-z0-9+/]+={0,2}$"); + public static JsonObject getJsonObject(List tsKv) { JsonObject json = new JsonObject(); for (KeyValueProto kv : tsKv) { @@ -56,7 +60,14 @@ public class JsonUtils { } else if (value instanceof Long) { return new JsonPrimitive((Long) value); } else if (value instanceof String) { - return JsonParser.parseString((String) value); + try { + return JsonParser.parseString((String) value); + } catch (Exception e) { + if (isBase64(value.toString())) { + value = "\"" + value + "\""; + } + return JsonParser.parseString((String) value); + } } else if (value instanceof Boolean) { return new JsonPrimitive((Boolean) value); } else if (value instanceof Double) { @@ -77,4 +88,7 @@ public class JsonUtils { return jsonObject; } + public static boolean isBase64(String value) { + return value.length() % 4 == 0 && BASE64_PATTERN.matcher(value).matches(); + } }