lwm2m: add params FOTA to object 19

This commit is contained in:
nickAS21 2025-04-09 18:48:31 +03:00
parent 8ef931f754
commit 5f2bb68bc8
4 changed files with 99 additions and 14 deletions

View File

@ -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.link.lwm2m.attributes.LwM2mAttributes.STEP;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; 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.convertMultiResourceValuesFromRpcBody;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.createModelsDefault; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.createModelsDefault;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
@ -399,7 +400,8 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
try { try {
Object valueForMultiResource = request.getValue(); Object valueForMultiResource = request.getValue();
if (resultIds.isResourceInstance()) { 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); valueForMultiResource = JsonParser.parseString(resourceInstance);
} }
Map<Integer, Object> value = convertMultiResourceValuesFromRpcBody(valueForMultiResource, resourceModelWrite.type, request.getObjectId()); Map<Integer, Object> value = convertMultiResourceValuesFromRpcBody(valueForMultiResource, resourceModelWrite.type, request.getObjectId());

View File

@ -15,16 +15,21 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.ota; 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.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; 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.node.codec.CodecException;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; 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.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; 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.attributes.LwM2MAttributesService;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService; 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.*;
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.log.LwM2MTelemetryLogService; 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.FirmwareDeliveryMethod;
import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; 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.LwM2MSoftwareUpdateStrategy;
import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; 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.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.store.TbLwM2MClientOtaInfoStore;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
@ -64,6 +67,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; 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_RESULT_ID = "/5/0/5";
public static final String FW_NAME_ID = "/5/0/6"; 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_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 * Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8
* Revision:BC68JAR01A10 * Revision:BC68JAR01A10
@ -134,6 +146,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
private final LwM2MTelemetryLogService logService; private final LwM2MTelemetryLogService logService;
private final LwM2mTransportServerHelper helper; private final LwM2mTransportServerHelper helper;
private final TbLwM2MClientOtaInfoStore otaInfoStore; private final TbLwM2MClientOtaInfoStore otaInfoStore;
private final LwM2mVersionedModelProvider modelProvider;
@Autowired @Autowired
@Lazy @Lazy
@ -510,6 +523,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
} else { } else {
strategy = info.getDeliveryMethod() == FirmwareDeliveryMethod.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; 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) { switch (strategy) {
case OBJ_5_BINARY: case OBJ_5_BINARY:
startUpdateUsingBinary(client, convertObjectIdToVersionedId(FW_PACKAGE_5_ID, client), otaPackageId); 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()); 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<OtaPackageUpdateStatus> toOtaPackageUpdateStatus(FirmwareUpdateResult fwUpdateResult) { private static Optional<OtaPackageUpdateStatus> toOtaPackageUpdateStatus(FirmwareUpdateResult fwUpdateResult) {
switch (fwUpdateResult) { switch (fwUpdateResult) {
case INITIAL: case INITIAL:

View File

@ -74,19 +74,17 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
} else { } else {
if (targetPackageId.equals(currentPackageId)) { if (targetPackageId.equals(currentPackageId)) {
return false; return false;
} else if (StringUtils.isNotEmpty(targetTag) && targetTag.equals(currentPackageId)) { } else{
if (targetTag.equals(currentPackageId)) {
return false; return false;
} else if (StringUtils.isNotEmpty(currentVersion3)) { } else if (StringUtils.isNotEmpty(currentVersion3)) {
if (StringUtils.isNotEmpty(targetTag) && currentVersion3.contains(targetTag)) { return !(currentVersion3.contains(targetTag) || targetTag.contains(currentVersion3));
return false; }
}
}
} }
return !currentVersion3.contains(targetPackageId);
} else {
return true; return true;
} }
}
}
}
@JsonIgnore @JsonIgnore
public boolean isSupported() { public boolean isSupported() {

View File

@ -23,9 +23,13 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern;
public class JsonUtils { public class JsonUtils {
private static final Pattern BASE64_PATTERN =
Pattern.compile("^[A-Za-z0-9+/]+={0,2}$");
public static JsonObject getJsonObject(List<KeyValueProto> tsKv) { public static JsonObject getJsonObject(List<KeyValueProto> tsKv) {
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
for (KeyValueProto kv : tsKv) { for (KeyValueProto kv : tsKv) {
@ -56,7 +60,14 @@ public class JsonUtils {
} else if (value instanceof Long) { } else if (value instanceof Long) {
return new JsonPrimitive((Long) value); return new JsonPrimitive((Long) value);
} else if (value instanceof String) { } else if (value instanceof String) {
try {
return JsonParser.parseString((String) value); return JsonParser.parseString((String) value);
} catch (Exception e) {
if (isBase64(value.toString())) {
value = "\"" + value + "\"";
}
return JsonParser.parseString((String) value);
}
} else if (value instanceof Boolean) { } else if (value instanceof Boolean) {
return new JsonPrimitive((Boolean) value); return new JsonPrimitive((Boolean) value);
} else if (value instanceof Double) { } else if (value instanceof Double) {
@ -77,4 +88,7 @@ public class JsonUtils {
return jsonObject; return jsonObject;
} }
public static boolean isBase64(String value) {
return value.length() % 4 == 0 && BASE64_PATTERN.matcher(value).matches();
}
} }