Commit abac309872abd59c86a35a31f2d13dbe78d7e8dc

Authored by Igor Kulikov
2 parents ea3c7139 1f621019

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 39 changed files with 295 additions and 180 deletions
... ... @@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
67 67 type varchar(32) NOT NULL,
68 68 title varchar(255) NOT NULL,
69 69 version varchar(255) NOT NULL,
  70 + tag varchar(255),
70 71 url varchar(255),
71 72 file_name varchar(255),
72 73 content_type varchar(255),
... ...
... ... @@ -59,6 +59,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
59 59 import org.thingsboard.server.common.msg.queue.TbCallback;
60 60 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
61 61 import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
  62 +import org.thingsboard.server.gen.transport.TransportProtos;
62 63 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
63 64 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
64 65 import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
... ... @@ -202,7 +203,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
202 203 syncSessionSet.add(key);
203 204 }
204 205 });
205   - log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions);
  206 + log.trace("Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions);
206 207 syncSessionSet.forEach(rpcSubscriptions::remove);
207 208 }
208 209
... ... @@ -318,7 +319,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
318 319 .setOneway(request.isOneway())
319 320 .setPersisted(request.isPersisted())
320 321 .build();
321   -
322 322 sendToTransport(rpcRequest, sessionId, nodeId);
323 323 };
324 324 }
... ... @@ -355,9 +355,26 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
355 355 if (msg.hasPersistedRpcResponseMsg()) {
356 356 processPersistedRpcResponses(context, sessionInfo, msg.getPersistedRpcResponseMsg());
357 357 }
  358 + if (msg.hasUplinkNotificationMsg()) {
  359 + processUplinkNotificationMsg(context, sessionInfo, msg.getUplinkNotificationMsg());
  360 + }
358 361 callback.onSuccess();
359 362 }
360 363
  364 + private void processUplinkNotificationMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg uplinkNotificationMsg) {
  365 + String nodeId = sessionInfo.getNodeId();
  366 + sessions.entrySet().stream()
  367 + .filter(kv -> kv.getValue().getSessionInfo().getNodeId().equals(nodeId) && (kv.getValue().isSubscribedToAttributes() || kv.getValue().isSubscribedToRPC()))
  368 + .forEach(kv -> {
  369 + ToTransportMsg msg = ToTransportMsg.newBuilder()
  370 + .setSessionIdMSB(kv.getKey().getMostSignificantBits())
  371 + .setSessionIdLSB(kv.getKey().getLeastSignificantBits())
  372 + .setUplinkNotificationMsg(uplinkNotificationMsg)
  373 + .build();
  374 + systemContext.getTbCoreToTransportService().process(kv.getValue().getSessionInfo().getNodeId(), msg);
  375 + });
  376 + }
  377 +
361 378 private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) {
362 379 DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
363 380 systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
... ... @@ -599,7 +616,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
599 616
600 617 void processCredentialsUpdate(TbActorMsg msg) {
601 618 if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
602   - log.info("1) LwM2Mtype: ");
603 619 sessions.forEach((k, v) -> {
604 620 notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
605 621 });
... ... @@ -616,7 +632,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
616 632 notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!");
617 633 }
618 634
619   -
620 635 private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) {
621 636 SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto
622 637 .newBuilder()
... ... @@ -630,7 +645,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
630 645 }
631 646
632 647 void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) {
633   - log.info("2) LwM2Mtype: ");
634 648 ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder();
635 649 notification.addCredentialsId(deviceCredentials.getCredentialsId());
636 650 notification.addCredentialsValue(deviceCredentials.getCredentialsValue());
... ...
... ... @@ -146,6 +146,7 @@ public class OtaPackageController extends BaseController {
146 146 otaPackage.setType(info.getType());
147 147 otaPackage.setTitle(info.getTitle());
148 148 otaPackage.setVersion(info.getVersion());
  149 + otaPackage.setTag(info.getTag());
149 150 otaPackage.setAdditionalInfo(info.getAdditionalInfo());
150 151
151 152 ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase());
... ...
... ... @@ -64,6 +64,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM;
64 64 import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM_ALGORITHM;
65 65 import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE;
66 66 import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
  67 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.TAG;
67 68 import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE;
68 69 import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS;
69 70 import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL;
... ... @@ -246,6 +247,11 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
246 247 List<TsKvEntry> telemetry = new ArrayList<>();
247 248 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle())));
248 249 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion())));
  250 +
  251 + if (StringUtils.isNotEmpty(firmware.getTag())) {
  252 + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TAG), firmware.getTag())));
  253 + }
  254 +
249 255 telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts)));
250 256 telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name())));
251 257
... ... @@ -289,6 +295,9 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
289 295 List<AttributeKvEntry> attributes = new ArrayList<>();
290 296 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
291 297 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
  298 + if (StringUtils.isNotEmpty(otaPackage.getTag())) {
  299 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TAG), otaPackage.getTag())));
  300 + }
292 301 if (otaPackage.hasUrl()) {
293 302 attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
294 303 List<String> attrToRemove = new ArrayList<>();
... ...
... ... @@ -36,6 +36,12 @@ public class DataConstants {
36 36 public static final String ALARM_CONDITION_REPEATS = "alarmConditionRepeats";
37 37 public static final String ALARM_CONDITION_DURATION = "alarmConditionDuration";
38 38 public static final String PERSISTENT = "persistent";
  39 + public static final String COAP_TRANSPORT_NAME = "COAP";
  40 + public static final String LWM2M_TRANSPORT_NAME = "LWM2M";
  41 + public static final String MQTT_TRANSPORT_NAME = "MQTT";
  42 + public static final String HTTP_TRANSPORT_NAME = "HTTP";
  43 + public static final String SNMP_TRANSPORT_NAME = "SNMP";
  44 +
39 45
40 46 public static final String[] allScopes() {
41 47 return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE};
... ...
... ... @@ -37,6 +37,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
37 37 private OtaPackageType type;
38 38 private String title;
39 39 private String version;
  40 + private String tag;
40 41 private String url;
41 42 private boolean hasData;
42 43 private String fileName;
... ... @@ -61,6 +62,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
61 62 this.type = otaPackageInfo.getType();
62 63 this.title = otaPackageInfo.getTitle();
63 64 this.version = otaPackageInfo.getVersion();
  65 + this.tag = otaPackageInfo.getTag();
64 66 this.url = otaPackageInfo.getUrl();
65 67 this.hasData = otaPackageInfo.isHasData();
66 68 this.fileName = otaPackageInfo.getFileName();
... ...
... ... @@ -19,7 +19,7 @@ import lombok.Getter;
19 19
20 20 public enum OtaPackageKey {
21 21
22   - TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"), URL("url");
  22 + TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"), URL("url"), TAG("tag");
23 23
24 24 @Getter
25 25 private final String value;
... ...
... ... @@ -73,6 +73,7 @@ public class HashPartitionService implements PartitionService {
73 73
74 74 private Map<String, TopicPartitionInfo> tbCoreNotificationTopics = new HashMap<>();
75 75 private Map<String, TopicPartitionInfo> tbRuleEngineNotificationTopics = new HashMap<>();
  76 + private Map<String, List<ServiceInfo>> tbTransportServicesByType = new HashMap<>();
76 77 private List<ServiceInfo> currentOtherServices;
77 78
78 79 private HashFunction hashFunction;
... ... @@ -127,6 +128,7 @@ public class HashPartitionService implements PartitionService {
127 128
128 129 @Override
129 130 public synchronized void recalculatePartitions(ServiceInfo currentService, List<ServiceInfo> otherServices) {
  131 + tbTransportServicesByType.clear();
130 132 logServiceInfo(currentService);
131 133 otherServices.forEach(this::logServiceInfo);
132 134 Map<ServiceQueueKey, List<ServiceInfo>> queueServicesMap = new HashMap<>();
... ... @@ -229,6 +231,12 @@ public class HashPartitionService implements PartitionService {
229 231 return Math.abs(hash % partitions);
230 232 }
231 233
  234 + @Override
  235 + public int countTransportsByType(String type) {
  236 + var list = tbTransportServicesByType.get(type);
  237 + return list == null ? 0 : list.size();
  238 + }
  239 +
232 240 private Map<ServiceQueueKey, List<ServiceInfo>> getServiceKeyListMap(List<ServiceInfo> services) {
233 241 final Map<ServiceQueueKey, List<ServiceInfo>> currentMap = new HashMap<>();
234 242 services.forEach(serviceInfo -> {
... ... @@ -332,6 +340,9 @@ public class HashPartitionService implements PartitionService {
332 340 queueServiceList.computeIfAbsent(serviceQueueKey, key -> new ArrayList<>()).add(instance);
333 341 }
334 342 }
  343 + for (String transportType : instance.getTransportsList()) {
  344 + tbTransportServicesByType.computeIfAbsent(transportType, t -> new ArrayList<>()).add(instance);
  345 + }
335 346 }
336 347
337 348 private ServiceInfo resolveByPartitionIdx(List<ServiceInfo> servers, Integer partitionIdx) {
... ...
... ... @@ -59,4 +59,6 @@ public interface PartitionService {
59 59 TopicPartitionInfo getNotificationsTopic(ServiceType serviceType, String serviceId);
60 60
61 61 int resolvePartitionIndex(UUID entityId, int partitions);
  62 +
  63 + int countTransportsByType(String type);
62 64 }
... ...
... ... @@ -341,6 +341,10 @@ message ToDeviceRpcResponseMsg {
341 341 string payload = 2;
342 342 }
343 343
  344 +message UplinkNotificationMsg {
  345 + int64 uplinkTs = 1;
  346 +}
  347 +
344 348 message ToDevicePersistedRpcResponseMsg {
345 349 int32 requestId = 1;
346 350 int64 requestIdMSB = 2;
... ... @@ -453,6 +457,7 @@ message TransportToDeviceActorMsg {
453 457 ProvisionDeviceRequestMsg provisionDevice = 9;
454 458 ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 10;
455 459 SendPendingRPCMsg sendPendingRPC = 11;
  460 + UplinkNotificationMsg uplinkNotificationMsg = 12;
456 461 }
457 462
458 463 message TransportToRuleEngineMsg {
... ... @@ -713,6 +718,7 @@ message ToTransportMsg {
713 718 ToTransportUpdateCredentialsProto toTransportUpdateCredentialsNotification = 11;
714 719 ResourceUpdateMsg resourceUpdateMsg = 12;
715 720 ResourceDeleteMsg resourceDeleteMsg = 13;
  721 + UplinkNotificationMsg uplinkNotificationMsg = 14;
716 722 }
717 723
718 724 message UsageStatsKVProto{
... ...
... ... @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
22 22 import org.springframework.stereotype.Service;
23 23 import org.thingsboard.server.coapserver.CoapServerService;
24 24 import org.thingsboard.server.coapserver.TbCoapServerComponent;
  25 +import org.thingsboard.server.common.data.DataConstants;
25 26 import org.thingsboard.server.common.data.TbTransportService;
26 27 import org.thingsboard.server.common.data.ota.OtaPackageType;
27 28 import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource;
... ... @@ -72,6 +73,6 @@ public class CoapTransportService implements TbTransportService {
72 73
73 74 @Override
74 75 public String getName() {
75   - return "COAP";
  76 + return DataConstants.COAP_TRANSPORT_NAME;
76 77 }
77 78 }
... ...
... ... @@ -18,14 +18,13 @@ package org.thingsboard.server.transport.coap.client;
18 18 import lombok.RequiredArgsConstructor;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.eclipse.californium.core.coap.CoAP;
21   -import org.eclipse.californium.core.coap.MediaTypeRegistry;
22 21 import org.eclipse.californium.core.coap.Response;
23 22 import org.eclipse.californium.core.observe.ObserveRelation;
24 23 import org.eclipse.californium.core.server.resources.CoapExchange;
25 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
26 25 import org.springframework.stereotype.Service;
27 26 import org.thingsboard.server.coapserver.CoapServerContext;
28   -import org.thingsboard.server.coapserver.TbCoapServerComponent;
  27 +import org.thingsboard.server.common.data.DataConstants;
29 28 import org.thingsboard.server.common.data.Device;
30 29 import org.thingsboard.server.common.data.DeviceProfile;
31 30 import org.thingsboard.server.common.data.DeviceTransportType;
... ... @@ -51,6 +50,7 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
51 50 import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
52 51 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
53 52 import org.thingsboard.server.gen.transport.TransportProtos;
  53 +import org.thingsboard.server.queue.discovery.PartitionService;
54 54 import org.thingsboard.server.transport.coap.CoapTransportContext;
55 55 import org.thingsboard.server.transport.coap.TbCoapMessageObserver;
56 56 import org.thingsboard.server.transport.coap.TransportConfigurationContainer;
... ... @@ -81,6 +81,7 @@ public class DefaultCoapClientContext implements CoapClientContext {
81 81 private final CoapTransportContext transportContext;
82 82 private final TransportService transportService;
83 83 private final TransportDeviceProfileCache profileCache;
  84 + private final PartitionService partitionService;
84 85 private final ConcurrentMap<DeviceId, TbCoapClientState> clients = new ConcurrentHashMap<>();
85 86 private final ConcurrentMap<String, TbCoapClientState> clientsByToken = new ConcurrentHashMap<>();
86 87
... ... @@ -161,7 +162,7 @@ public class DefaultCoapClientContext implements CoapClientContext {
161 162 }
162 163 }
163 164
164   - private void onUplink(TbCoapClientState client) {
  165 + private void onUplink(TbCoapClientState client, boolean notifyOtherServers, long uplinkTs) {
165 166 PowerMode powerMode = client.getPowerMode();
166 167 PowerSavingConfiguration profileSettings = null;
167 168 if (powerMode == null) {
... ... @@ -174,12 +175,12 @@ public class DefaultCoapClientContext implements CoapClientContext {
174 175 }
175 176 }
176 177 if (powerMode == null || PowerMode.DRX.equals(powerMode)) {
177   - client.updateLastUplinkTime();
  178 + client.updateLastUplinkTime(uplinkTs);
178 179 return;
179 180 }
180 181 client.lock();
181 182 try {
182   - long uplinkTime = client.updateLastUplinkTime();
  183 + long uplinkTime = client.updateLastUplinkTime(uplinkTs);
183 184 long timeout;
184 185 if (PowerMode.PSM.equals(powerMode)) {
185 186 Long psmActivityTimer = client.getPsmActivityTimer();
... ... @@ -214,6 +215,9 @@ public class DefaultCoapClientContext implements CoapClientContext {
214 215 return null;
215 216 }, timeout, TimeUnit.MILLISECONDS);
216 217 client.setSleepTask(task);
  218 + if (notifyOtherServers && partitionService.countTransportsByType(DataConstants.COAP_TRANSPORT_NAME) > 1) {
  219 + transportService.notifyAboutUplink(getNewSyncSession(client), TransportProtos.UplinkNotificationMsg.newBuilder().setUplinkTs(uplinkTime).build(), TransportServiceCallback.EMPTY);
  220 + }
217 221 } finally {
218 222 client.unlock();
219 223 }
... ... @@ -544,6 +548,11 @@ public class DefaultCoapClientContext implements CoapClientContext {
544 548 log.trace("[{}] Received server rpc response in the wrong session.", state.getSession());
545 549 }
546 550
  551 + @Override
  552 + public void onUplinkNotification(TransportProtos.UplinkNotificationMsg notificationMsg) {
  553 + awake(state, false, notificationMsg.getUplinkTs());
  554 + }
  555 +
547 556 private void cancelObserveRelation(TbCoapObservationState attrs) {
548 557 if (attrs.getObserveRelation() != null) {
549 558 attrs.getObserveRelation().cancel();
... ... @@ -562,7 +571,11 @@ public class DefaultCoapClientContext implements CoapClientContext {
562 571
563 572 @Override
564 573 public boolean awake(TbCoapClientState client) {
565   - onUplink(client);
  574 + return awake(client, true, System.currentTimeMillis());
  575 + }
  576 +
  577 + private boolean awake(TbCoapClientState client, boolean notifyOtherServers, long uplinkTs) {
  578 + onUplink(client, notifyOtherServers, uplinkTs);
566 579 boolean changed = compareAndSetSleepFlag(client, false);
567 580 if (changed) {
568 581 log.debug("[{}] client is awake", client.getDeviceId());
... ...
... ... @@ -97,9 +97,11 @@ public class TbCoapClientState {
97 97 lock.unlock();
98 98 }
99 99
100   - public long updateLastUplinkTime() {
101   - this.lastUplinkTime = System.currentTimeMillis();
102   - this.firstEdrxDownlink = true;
  100 + public long updateLastUplinkTime(long ts) {
  101 + if (ts > lastUplinkTime) {
  102 + this.lastUplinkTime = ts;
  103 + this.firstEdrxDownlink = true;
  104 + }
103 105 return lastUplinkTime;
104 106 }
105 107
... ...
... ... @@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
34 34 import org.springframework.web.bind.annotation.RequestParam;
35 35 import org.springframework.web.bind.annotation.RestController;
36 36 import org.springframework.web.context.request.async.DeferredResult;
  37 +import org.thingsboard.server.common.data.DataConstants;
37 38 import org.thingsboard.server.common.data.DeviceTransportType;
38 39 import org.thingsboard.server.common.data.TbTransportService;
39 40 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -436,7 +437,7 @@ public class DeviceApiController implements TbTransportService {
436 437
437 438 @Override
438 439 public String getName() {
439   - return "HTTP";
  440 + return DataConstants.HTTP_TRANSPORT_NAME;
440 441 }
441 442
442 443 }
... ...
... ... @@ -28,6 +28,7 @@ import org.eclipse.leshan.server.californium.registration.CaliforniumRegistratio
28 28 import org.eclipse.leshan.server.model.LwM2mModelProvider;
29 29 import org.springframework.stereotype.Component;
30 30 import org.thingsboard.server.cache.ota.OtaPackageDataCache;
  31 +import org.thingsboard.server.common.data.DataConstants;
31 32 import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
32 33 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
33 34 import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer;
... ... @@ -177,7 +178,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
177 178
178 179 @Override
179 180 public String getName() {
180   - return "LWM2M";
  181 + return DataConstants.LWM2M_TRANSPORT_NAME;
181 182 }
182 183
183 184 }
... ...
... ... @@ -120,9 +120,11 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
120 120 if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) {
121 121 String newFirmwareTitle = null;
122 122 String newFirmwareVersion = null;
  123 + String newFirmwareTag = null;
123 124 String newFirmwareUrl = null;
124 125 String newSoftwareTitle = null;
125 126 String newSoftwareVersion = null;
  127 + String newSoftwareTag = null;
126 128 String newSoftwareUrl = null;
127 129 List<TransportProtos.TsKvProto> otherAttributes = new ArrayList<>();
128 130 for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) {
... ... @@ -131,12 +133,16 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
131 133 newFirmwareTitle = getStrValue(tsKvProto);
132 134 } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION.equals(attrName)) {
133 135 newFirmwareVersion = getStrValue(tsKvProto);
  136 + } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_TAG.equals(attrName)) {
  137 + newFirmwareTag = getStrValue(tsKvProto);
134 138 } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_URL.equals(attrName)) {
135 139 newFirmwareUrl = getStrValue(tsKvProto);
136 140 } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE.equals(attrName)) {
137 141 newSoftwareTitle = getStrValue(tsKvProto);
138 142 } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) {
139 143 newSoftwareVersion = getStrValue(tsKvProto);
  144 + } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TAG.equals(attrName)) {
  145 + newSoftwareTag = getStrValue(tsKvProto);
140 146 } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) {
141 147 newSoftwareUrl = getStrValue(tsKvProto);
142 148 }else {
... ... @@ -144,10 +150,10 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
144 150 }
145 151 }
146 152 if (newFirmwareTitle != null || newFirmwareVersion != null) {
147   - otaUpdateService.onTargetFirmwareUpdate(lwM2MClient, newFirmwareTitle, newFirmwareVersion, Optional.ofNullable(newFirmwareUrl));
  153 + otaUpdateService.onTargetFirmwareUpdate(lwM2MClient, newFirmwareTitle, newFirmwareVersion, Optional.ofNullable(newFirmwareUrl), Optional.ofNullable(newFirmwareTag));
148 154 }
149 155 if (newSoftwareTitle != null || newSoftwareVersion != null) {
150   - otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl));
  156 + otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag));
151 157 }
152 158 if (!otherAttributes.isEmpty()) {
153 159 onAttributesUpdate(lwM2MClient, otherAttributes);
... ...
... ... @@ -85,9 +85,11 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
85 85
86 86 public static final String FIRMWARE_VERSION = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION);
87 87 public static final String FIRMWARE_TITLE = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE);
  88 + public static final String FIRMWARE_TAG = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TAG);
88 89 public static final String FIRMWARE_URL = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.URL);
89 90 public static final String SOFTWARE_VERSION = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION);
90 91 public static final String SOFTWARE_TITLE = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE);
  92 + public static final String SOFTWARE_TAG = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TAG);
91 93 public static final String SOFTWARE_URL = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.URL);
92 94
93 95 public static final String FIRMWARE_UPDATE_COAP_RESOURCE = "tbfw";
... ... @@ -165,6 +167,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
165 167 if (fwInfo.isSupported()) {
166 168 attributesToFetch.add(FIRMWARE_TITLE);
167 169 attributesToFetch.add(FIRMWARE_VERSION);
  170 + attributesToFetch.add(FIRMWARE_TAG);
168 171 attributesToFetch.add(FIRMWARE_URL);
169 172 }
170 173
... ... @@ -172,6 +175,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
172 175 if (swInfo.isSupported()) {
173 176 attributesToFetch.add(SOFTWARE_TITLE);
174 177 attributesToFetch.add(SOFTWARE_VERSION);
  178 + attributesToFetch.add(SOFTWARE_TAG);
175 179 attributesToFetch.add(SOFTWARE_URL);
176 180 }
177 181
... ... @@ -186,17 +190,19 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
186 190 if (fwInfo.isSupported()) {
187 191 Optional<String> newFwTitle = getAttributeValue(attrs, FIRMWARE_TITLE);
188 192 Optional<String> newFwVersion = getAttributeValue(attrs, FIRMWARE_VERSION);
  193 + Optional<String> newFwTag = getAttributeValue(attrs, FIRMWARE_TAG);
189 194 Optional<String> newFwUrl = getAttributeValue(attrs, FIRMWARE_URL);
190 195 if (newFwTitle.isPresent() && newFwVersion.isPresent()) {
191   - onTargetFirmwareUpdate(client, newFwTitle.get(), newFwVersion.get(), newFwUrl);
  196 + onTargetFirmwareUpdate(client, newFwTitle.get(), newFwVersion.get(), newFwUrl, newFwTag);
192 197 }
193 198 }
194 199 if (swInfo.isSupported()) {
195 200 Optional<String> newSwTitle = getAttributeValue(attrs, SOFTWARE_TITLE);
196 201 Optional<String> newSwVersion = getAttributeValue(attrs, SOFTWARE_VERSION);
  202 + Optional<String> newSwTag = getAttributeValue(attrs, SOFTWARE_TAG);
197 203 Optional<String> newSwUrl = getAttributeValue(attrs, SOFTWARE_URL);
198 204 if (newSwTitle.isPresent() && newSwVersion.isPresent()) {
199   - onTargetSoftwareUpdate(client, newSwTitle.get(), newSwVersion.get(), newSwUrl);
  205 + onTargetSoftwareUpdate(client, newSwTitle.get(), newSwVersion.get(), newSwUrl, newSwTag);
200 206 }
201 207 }
202 208 }, throwable -> {
... ... @@ -216,9 +222,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
216 222 }
217 223
218 224 @Override
219   - public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional<String> newFirmwareUrl) {
  225 + public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional<String> newFirmwareUrl, Optional<String> newFirmwareTag) {
220 226 LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client);
221   - fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl);
  227 + fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl, newFirmwareTag);
222 228 update(fwInfo);
223 229 startFirmwareUpdateIfNeeded(client, fwInfo);
224 230 }
... ... @@ -354,9 +360,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
354 360 }
355 361
356 362 @Override
357   - public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion, Optional<String> newFirmwareUrl) {
  363 + public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion, Optional<String> newSoftwareUrl, Optional<String> newSoftwareTag) {
358 364 LwM2MClientSwOtaInfo fwInfo = getOrInitSwInfo(client);
359   - fwInfo.updateTarget(newSoftwareTitle, newSoftwareVersion, newFirmwareUrl);
  365 + fwInfo.updateTarget(newSoftwareTitle, newSoftwareVersion, newSoftwareUrl, newSoftwareTag);
360 366 update(fwInfo);
361 367 startSoftwareUpdateIfNeeded(client, fwInfo);
362 368 }
... ... @@ -368,7 +374,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
368 374 sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!");
369 375 } else if (fwInfo.isUpdateRequired()) {
370 376 if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) {
371   - log.debug("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl());
  377 + log.debug("[{}] Starting update to [{}{}][] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl());
372 378 startUpdateUsingUrl(client, FW_URL_ID, fwInfo.getTargetUrl());
373 379 } else {
374 380 log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion());
... ...
... ... @@ -32,6 +32,7 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
32 32
33 33 protected String targetName;
34 34 protected String targetVersion;
  35 + protected String targetTag;
35 36 protected String targetUrl;
36 37
37 38 //TODO: use value from device if applicable;
... ... @@ -52,10 +53,11 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
52 53 this.strategy = strategy;
53 54 }
54 55
55   - public void updateTarget(String targetName, String targetVersion, Optional<String> newTargetUrl) {
  56 + public void updateTarget(String targetName, String targetVersion, Optional<String> newTargetUrl, Optional<String> newTargetTag) {
56 57 this.targetName = targetName;
57 58 this.targetVersion = targetVersion;
58 59 this.targetUrl = newTargetUrl.orElse(null);
  60 + this.targetTag = newTargetTag.orElse(null);
59 61 }
60 62
61 63 @JsonIgnore
... ... @@ -64,13 +66,18 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
64 66 return false;
65 67 } else {
66 68 String targetPackageId = getPackageId(targetName, targetVersion);
67   - String currentPackageIdUsingObject5 = getPackageId(currentName, currentVersion);
  69 + String currentPackageId = getPackageId(currentName, currentVersion);
68 70 if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) {
69 71 return false;
70 72 } else {
71   - if (targetPackageId.equals(currentPackageIdUsingObject5)) {
  73 + if (targetPackageId.equals(currentPackageId)) {
  74 + return false;
  75 + } else if (StringUtils.isNotEmpty(targetTag) && targetTag.equals(currentPackageId)) {
72 76 return false;
73 77 } else if (StringUtils.isNotEmpty(currentVersion3)) {
  78 + if (StringUtils.isNotEmpty(targetTag) && currentVersion3.contains(targetTag)) {
  79 + return false;
  80 + }
74 81 return !currentVersion3.contains(targetPackageId);
75 82 } else {
76 83 return true;
... ...
... ... @@ -26,9 +26,9 @@ public interface LwM2MOtaUpdateService {
26 26
27 27 void forceFirmwareUpdate(LwM2mClient client);
28 28
29   - void onTargetFirmwareUpdate(LwM2mClient client, String newFwTitle, String newFwVersion, Optional<String> newFwUrl);
  29 + void onTargetFirmwareUpdate(LwM2mClient client, String newFwTitle, String newFwVersion, Optional<String> newFwUrl, Optional<String> newFwTag);
30 30
31   - void onTargetSoftwareUpdate(LwM2mClient client, String newSwTitle, String newSwVersion, Optional<String> newSwUrl);
  31 + void onTargetSoftwareUpdate(LwM2mClient client, String newSwTitle, String newSwVersion, Optional<String> newSwUrl, Optional<String> newSwTag);
32 32
33 33 void onCurrentFirmwareNameUpdate(LwM2mClient client, String name);
34 34
... ...
... ... @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
28 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
29 29 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30 30 import org.springframework.stereotype.Service;
  31 +import org.thingsboard.server.common.data.DataConstants;
31 32 import org.thingsboard.server.common.data.TbTransportService;
32 33
33 34 import javax.annotation.PostConstruct;
... ... @@ -114,6 +115,6 @@ public class MqttTransportService implements TbTransportService {
114 115
115 116 @Override
116 117 public String getName() {
117   - return "MQTT";
  118 + return DataConstants.MQTT_TRANSPORT_NAME;
118 119 }
119 120 }
... ...
... ... @@ -35,6 +35,7 @@ import org.snmp4j.transport.DefaultUdpTransportMapping;
35 35 import org.springframework.beans.factory.annotation.Value;
36 36 import org.springframework.stereotype.Service;
37 37 import org.thingsboard.common.util.ThingsBoardThreadFactory;
  38 +import org.thingsboard.server.common.data.DataConstants;
38 39 import org.thingsboard.server.common.data.TbTransportService;
39 40 import org.thingsboard.server.common.data.kv.DataType;
40 41 import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
... ... @@ -300,7 +301,7 @@ public class SnmpTransportService implements TbTransportService {
300 301
301 302 @Override
302 303 public String getName() {
303   - return "SNMP";
  304 + return DataConstants.SNMP_TRANSPORT_NAME;
304 305 }
305 306
306 307 @PreDestroy
... ...
... ... @@ -25,6 +25,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotifica
25 25 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
26 26 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
27 27 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
  28 +import org.thingsboard.server.gen.transport.TransportProtos.UplinkNotificationMsg;
28 29
29 30 import java.util.Optional;
30 31 import java.util.UUID;
... ... @@ -44,6 +45,8 @@ public interface SessionMsgListener {
44 45
45 46 void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse);
46 47
  48 + default void onUplinkNotification(UplinkNotificationMsg notificationMsg){};
  49 +
47 50 default void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto toTransportUpdateCredentials){}
48 51
49 52 default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) {}
... ...
... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
20 20 import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
21 21 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
22 22 import org.thingsboard.server.common.transport.service.SessionMetaData;
  23 +import org.thingsboard.server.gen.transport.TransportProtos;
23 24 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
24 25 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
25 26 import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg;
... ... @@ -128,4 +129,6 @@ public interface TransportService {
128 129 void deregisterSession(SessionInfoProto sessionInfo);
129 130
130 131 void log(SessionInfoProto sessionInfo, String msg);
  132 +
  133 + void notifyAboutUplink(SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg build, TransportServiceCallback<Void> empty);
131 134 }
... ...
... ... @@ -572,6 +572,14 @@ public class DefaultTransportService implements TransportService {
572 572 }
573 573
574 574 @Override
  575 + public void notifyAboutUplink(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg msg, TransportServiceCallback<Void> callback) {
  576 + if (checkLimits(sessionInfo, msg, callback)) {
  577 + reportActivityInternal(sessionInfo);
  578 + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setUplinkNotificationMsg(msg).build(), callback);
  579 + }
  580 + }
  581 +
  582 + @Override
575 583 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcRequestMsg msg, boolean isFailedRpc, TransportServiceCallback<Void> callback) {
576 584 if (msg.getPersisted()) {
577 585 RpcStatus status;
... ...
... ... @@ -499,6 +499,7 @@ public class ModelConstants {
499 499 public static final String OTA_PACKAGE_TYPE_COLUMN = "type";
500 500 public static final String OTA_PACKAGE_TILE_COLUMN = TITLE_PROPERTY;
501 501 public static final String OTA_PACKAGE_VERSION_COLUMN = "version";
  502 + public static final String OTA_PACKAGE_TAG_COLUMN = "tag";
502 503 public static final String OTA_PACKAGE_URL_COLUMN = "url";
503 504 public static final String OTA_PACKAGE_FILE_NAME_COLUMN = "file_name";
504 505 public static final String OTA_PACKAGE_CONTENT_TYPE_COLUMN = "content_type";
... ...
... ... @@ -48,6 +48,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DATA_S
48 48 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN;
49 49 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_FILE_NAME_COLUMN;
50 50 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_NAME;
  51 +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TAG_COLUMN;
51 52 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
52 53 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
53 54 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
... ... @@ -78,6 +79,9 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
78 79 @Column(name = OTA_PACKAGE_VERSION_COLUMN)
79 80 private String version;
80 81
  82 + @Column(name = OTA_PACKAGE_TAG_COLUMN)
  83 + private String tag;
  84 +
81 85 @Column(name = OTA_PACKAGE_URL_COLUMN)
82 86 private String url;
83 87
... ... @@ -112,24 +116,25 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
112 116 super();
113 117 }
114 118
115   - public OtaPackageEntity(OtaPackage firmware) {
116   - this.createdTime = firmware.getCreatedTime();
117   - this.setUuid(firmware.getUuidId());
118   - this.tenantId = firmware.getTenantId().getId();
119   - if (firmware.getDeviceProfileId() != null) {
120   - this.deviceProfileId = firmware.getDeviceProfileId().getId();
  119 + public OtaPackageEntity(OtaPackage otaPackage) {
  120 + this.createdTime = otaPackage.getCreatedTime();
  121 + this.setUuid(otaPackage.getUuidId());
  122 + this.tenantId = otaPackage.getTenantId().getId();
  123 + if (otaPackage.getDeviceProfileId() != null) {
  124 + this.deviceProfileId = otaPackage.getDeviceProfileId().getId();
121 125 }
122   - this.type = firmware.getType();
123   - this.title = firmware.getTitle();
124   - this.version = firmware.getVersion();
125   - this.url = firmware.getUrl();
126   - this.fileName = firmware.getFileName();
127   - this.contentType = firmware.getContentType();
128   - this.checksumAlgorithm = firmware.getChecksumAlgorithm();
129   - this.checksum = firmware.getChecksum();
130   - this.data = firmware.getData().array();
131   - this.dataSize = firmware.getDataSize();
132   - this.additionalInfo = firmware.getAdditionalInfo();
  126 + this.type = otaPackage.getType();
  127 + this.title = otaPackage.getTitle();
  128 + this.version = otaPackage.getVersion();
  129 + this.tag = otaPackage.getTag();
  130 + this.url = otaPackage.getUrl();
  131 + this.fileName = otaPackage.getFileName();
  132 + this.contentType = otaPackage.getContentType();
  133 + this.checksumAlgorithm = otaPackage.getChecksumAlgorithm();
  134 + this.checksum = otaPackage.getChecksum();
  135 + this.data = otaPackage.getData().array();
  136 + this.dataSize = otaPackage.getDataSize();
  137 + this.additionalInfo = otaPackage.getAdditionalInfo();
133 138 }
134 139
135 140 @Override
... ... @@ -144,26 +149,27 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
144 149
145 150 @Override
146 151 public OtaPackage toData() {
147   - OtaPackage firmware = new OtaPackage(new OtaPackageId(id));
148   - firmware.setCreatedTime(createdTime);
149   - firmware.setTenantId(new TenantId(tenantId));
  152 + OtaPackage otaPackage = new OtaPackage(new OtaPackageId(id));
  153 + otaPackage.setCreatedTime(createdTime);
  154 + otaPackage.setTenantId(new TenantId(tenantId));
150 155 if (deviceProfileId != null) {
151   - firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
  156 + otaPackage.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
152 157 }
153   - firmware.setType(type);
154   - firmware.setTitle(title);
155   - firmware.setVersion(version);
156   - firmware.setUrl(url);
157   - firmware.setFileName(fileName);
158   - firmware.setContentType(contentType);
159   - firmware.setChecksumAlgorithm(checksumAlgorithm);
160   - firmware.setChecksum(checksum);
161   - firmware.setDataSize(dataSize);
  158 + otaPackage.setType(type);
  159 + otaPackage.setTitle(title);
  160 + otaPackage.setVersion(version);
  161 + otaPackage.setTag(tag);
  162 + otaPackage.setUrl(url);
  163 + otaPackage.setFileName(fileName);
  164 + otaPackage.setContentType(contentType);
  165 + otaPackage.setChecksumAlgorithm(checksumAlgorithm);
  166 + otaPackage.setChecksum(checksum);
  167 + otaPackage.setDataSize(dataSize);
162 168 if (data != null) {
163   - firmware.setData(ByteBuffer.wrap(data));
164   - firmware.setHasData(true);
  169 + otaPackage.setData(ByteBuffer.wrap(data));
  170 + otaPackage.setHasData(true);
165 171 }
166   - firmware.setAdditionalInfo(additionalInfo);
167   - return firmware;
  172 + otaPackage.setAdditionalInfo(additionalInfo);
  173 + return otaPackage;
168 174 }
169 175 }
... ...
... ... @@ -48,6 +48,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DATA_S
48 48 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN;
49 49 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_FILE_NAME_COLUMN;
50 50 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_NAME;
  51 +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TAG_COLUMN;
51 52 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
52 53 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
53 54 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
... ... @@ -78,6 +79,9 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
78 79 @Column(name = OTA_PACKAGE_VERSION_COLUMN)
79 80 private String version;
80 81
  82 + @Column(name = OTA_PACKAGE_TAG_COLUMN)
  83 + private String tag;
  84 +
81 85 @Column(name = OTA_PACKAGE_URL_COLUMN)
82 86 private String url;
83 87
... ... @@ -111,26 +115,27 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
111 115 super();
112 116 }
113 117
114   - public OtaPackageInfoEntity(OtaPackageInfo firmware) {
115   - this.createdTime = firmware.getCreatedTime();
116   - this.setUuid(firmware.getUuidId());
117   - this.tenantId = firmware.getTenantId().getId();
118   - this.type = firmware.getType();
119   - if (firmware.getDeviceProfileId() != null) {
120   - this.deviceProfileId = firmware.getDeviceProfileId().getId();
  118 + public OtaPackageInfoEntity(OtaPackageInfo otaPackageInfo) {
  119 + this.createdTime = otaPackageInfo.getCreatedTime();
  120 + this.setUuid(otaPackageInfo.getUuidId());
  121 + this.tenantId = otaPackageInfo.getTenantId().getId();
  122 + this.type = otaPackageInfo.getType();
  123 + if (otaPackageInfo.getDeviceProfileId() != null) {
  124 + this.deviceProfileId = otaPackageInfo.getDeviceProfileId().getId();
121 125 }
122   - this.title = firmware.getTitle();
123   - this.version = firmware.getVersion();
124   - this.url = firmware.getUrl();
125   - this.fileName = firmware.getFileName();
126   - this.contentType = firmware.getContentType();
127   - this.checksumAlgorithm = firmware.getChecksumAlgorithm();
128   - this.checksum = firmware.getChecksum();
129   - this.dataSize = firmware.getDataSize();
130   - this.additionalInfo = firmware.getAdditionalInfo();
  126 + this.title = otaPackageInfo.getTitle();
  127 + this.version = otaPackageInfo.getVersion();
  128 + this.tag = otaPackageInfo.getTag();
  129 + this.url = otaPackageInfo.getUrl();
  130 + this.fileName = otaPackageInfo.getFileName();
  131 + this.contentType = otaPackageInfo.getContentType();
  132 + this.checksumAlgorithm = otaPackageInfo.getChecksumAlgorithm();
  133 + this.checksum = otaPackageInfo.getChecksum();
  134 + this.dataSize = otaPackageInfo.getDataSize();
  135 + this.additionalInfo = otaPackageInfo.getAdditionalInfo();
131 136 }
132 137
133   - public OtaPackageInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, OtaPackageType type, String title, String version,
  138 + public OtaPackageInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, OtaPackageType type, String title, String version, String tag,
134 139 String url, String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
135 140 Object additionalInfo, boolean hasData) {
136 141 this.id = id;
... ... @@ -140,6 +145,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
140 145 this.type = type;
141 146 this.title = title;
142 147 this.version = version;
  148 + this.tag = tag;
143 149 this.url = url;
144 150 this.fileName = fileName;
145 151 this.contentType = contentType;
... ... @@ -162,23 +168,24 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
162 168
163 169 @Override
164 170 public OtaPackageInfo toData() {
165   - OtaPackageInfo firmware = new OtaPackageInfo(new OtaPackageId(id));
166   - firmware.setCreatedTime(createdTime);
167   - firmware.setTenantId(new TenantId(tenantId));
  171 + OtaPackageInfo otaPackageInfo = new OtaPackageInfo(new OtaPackageId(id));
  172 + otaPackageInfo.setCreatedTime(createdTime);
  173 + otaPackageInfo.setTenantId(new TenantId(tenantId));
168 174 if (deviceProfileId != null) {
169   - firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
  175 + otaPackageInfo.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
170 176 }
171   - firmware.setType(type);
172   - firmware.setTitle(title);
173   - firmware.setVersion(version);
174   - firmware.setUrl(url);
175   - firmware.setFileName(fileName);
176   - firmware.setContentType(contentType);
177   - firmware.setChecksumAlgorithm(checksumAlgorithm);
178   - firmware.setChecksum(checksum);
179   - firmware.setDataSize(dataSize);
180   - firmware.setAdditionalInfo(additionalInfo);
181   - firmware.setHasData(hasData);
182   - return firmware;
  177 + otaPackageInfo.setType(type);
  178 + otaPackageInfo.setTitle(title);
  179 + otaPackageInfo.setVersion(version);
  180 + otaPackageInfo.setTag(tag);
  181 + otaPackageInfo.setUrl(url);
  182 + otaPackageInfo.setFileName(fileName);
  183 + otaPackageInfo.setContentType(contentType);
  184 + otaPackageInfo.setChecksumAlgorithm(checksumAlgorithm);
  185 + otaPackageInfo.setChecksum(checksum);
  186 + otaPackageInfo.setDataSize(dataSize);
  187 + otaPackageInfo.setAdditionalInfo(additionalInfo);
  188 + otaPackageInfo.setHasData(hasData);
  189 + return otaPackageInfo;
183 190 }
184 191 }
... ...
... ... @@ -51,6 +51,7 @@ import org.thingsboard.server.dao.tenant.TenantDao;
51 51 import java.nio.ByteBuffer;
52 52 import java.util.Collections;
53 53 import java.util.List;
  54 +import java.util.Objects;
54 55 import java.util.Optional;
55 56
56 57 import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE;
... ... @@ -318,6 +319,10 @@ public class BaseOtaPackageService implements OtaPackageService {
318 319 throw new DataValidationException("Updating otaPackage version is prohibited!");
319 320 }
320 321
  322 + if (!Objects.equals(otaPackage.getTag(), otaPackageOld.getTag())) {
  323 + throw new DataValidationException("Updating otaPackage tag is prohibited!");
  324 + }
  325 +
321 326 if (!otaPackageOld.getDeviceProfileId().equals(otaPackage.getDeviceProfileId())) {
322 327 throw new DataValidationException("Updating otaPackage deviceProfile is prohibited!");
323 328 }
... ...
... ... @@ -26,14 +26,14 @@ import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity;
26 26 import java.util.UUID;
27 27
28 28 public interface OtaPackageInfoRepository extends CrudRepository<OtaPackageInfoEntity, UUID> {
29   - @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE " +
  29 + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.tag, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE " +
30 30 "f.tenantId = :tenantId " +
31 31 "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
32 32 Page<OtaPackageInfoEntity> findAllByTenantId(@Param("tenantId") UUID tenantId,
33 33 @Param("searchText") String searchText,
34 34 Pageable pageable);
35 35
36   - @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, true) FROM OtaPackageEntity f WHERE " +
  36 + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.tag, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, true) FROM OtaPackageEntity f WHERE " +
37 37 "f.tenantId = :tenantId " +
38 38 "AND f.deviceProfileId = :deviceProfileId " +
39 39 "AND f.type = :type " +
... ... @@ -45,7 +45,7 @@ public interface OtaPackageInfoRepository extends CrudRepository<OtaPackageInfoE
45 45 @Param("searchText") String searchText,
46 46 Pageable pageable);
47 47
48   - @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE f.id = :id")
  48 + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.tag, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE f.id = :id")
49 49 OtaPackageInfoEntity findOtaPackageInfoById(@Param("id") UUID id);
50 50
51 51 @Query(value = "SELECT exists(SELECT * " +
... ...
... ... @@ -173,6 +173,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
173 173 type varchar(32) NOT NULL,
174 174 title varchar(255) NOT NULL,
175 175 version varchar(255) NOT NULL,
  176 + tag varchar(255),
176 177 url varchar(255),
177 178 file_name varchar(255),
178 179 content_type varchar(255),
... ...
... ... @@ -188,6 +188,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
188 188 type varchar(32) NOT NULL,
189 189 title varchar(255) NOT NULL,
190 190 version varchar(255) NOT NULL,
  191 + tag varchar(255),
191 192 url varchar(255),
192 193 file_name varchar(255),
193 194 content_type varchar(255),
... ...
... ... @@ -174,14 +174,9 @@
174 174 <!-- <div fxLayout="column">-->
175 175 <!-- <mat-form-field class="mat-block">-->
176 176 <!-- <mat-label>{{ 'device-profile.lwm2m.client-strategy-label' | translate }}</mat-label>-->
177   -<!-- <mat-select formControlName="clientOnlyObserveAfterConnect"-->
178   -<!-- matTooltip="{{ 'device-profile.lwm2m.client-strategy-tip' | translate:-->
179   -<!-- { count: +lwm2mDeviceProfileFormGroup.get('clientOnlyObserveAfterConnect').value } }}"-->
180   -<!-- matTooltipPosition="above">-->
181   -<!-- <mat-option value=1>{{ 'device-profile.lwm2m.client-strategy-connect' | translate:-->
182   -<!-- {count: 1} }}</mat-option>-->
183   -<!-- <mat-option value=2>{{ 'device-profile.lwm2m.client-strategy-connect' | translate:-->
184   -<!-- {count: 2} }}</mat-option>-->
  177 +<!-- <mat-select formControlName="clientOnlyObserveAfterConnect">-->
  178 +<!-- <mat-option value=1>{{ 'device-profile.lwm2m.client-strategy-only-observe' | translate }}</mat-option>-->
  179 +<!-- <mat-option value=2>{{ 'device-profile.lwm2m.client-strategy-read-all' | translate }}</mat-option>-->
185 180 <!-- </mat-select>-->
186 181 <!-- </mat-form-field>-->
187 182 <!-- </div>-->
... ... @@ -194,13 +189,12 @@
194 189 </mat-tab>
195 190 <mat-tab label="{{ 'device-profile.lwm2m.config-json-tab' | translate }}">
196 191 <ng-template matTabContent>
197   - <section [formGroup]="lwm2mDeviceConfigFormGroup" style="padding: 8px 0">
  192 + <section style="padding: 8px 0">
198 193 <tb-json-object-edit
199 194 readonly
200   - [required]="required"
201 195 [sort]="sortFunction"
202 196 label="{{ 'device-profile.transport-type-lwm2m' | translate }}"
203   - formControlName="configurationJson">
  197 + [ngModel]="configurationValue">
204 198 </tb-json-object-edit>
205 199 </section>
206 200 </ng-template>
... ...
... ... @@ -14,7 +14,6 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { DeviceProfileTransportConfiguration } from '@shared/models/device.models';
18 17 import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
19 18 import {
20 19 ControlValueAccessor,
... ... @@ -76,7 +75,6 @@ import { takeUntil } from 'rxjs/operators';
76 75 })
77 76 export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validator, OnDestroy {
78 77
79   - private configurationValue: Lwm2mProfileConfigModels;
80 78 private requiredValue: boolean;
81 79 private disabled = false;
82 80 private destroy$ = new Subject();
... ... @@ -84,7 +82,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
84 82 bindingModeTypes = Object.values(BingingMode);
85 83 bindingModeTypeNamesMap = BingingModeTranslationsMap;
86 84 lwm2mDeviceProfileFormGroup: FormGroup;
87   - lwm2mDeviceConfigFormGroup: FormGroup;
  85 + configurationValue: Lwm2mProfileConfigModels;
88 86 sortFunction: (key: string, value: object) => object;
89 87
90 88 get required(): boolean {
... ... @@ -128,9 +126,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
128 126 compositeOperationsSupport: [false]
129 127 })
130 128 });
131   - this.lwm2mDeviceConfigFormGroup = this.fb.group({
132   - configurationJson: [null, Validators.required]
133   - });
134 129 this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').valueChanges.pipe(
135 130 takeUntil(this.destroy$)
136 131 ).subscribe((fwStrategy) => {
... ... @@ -158,11 +153,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
158 153 ).subscribe((value) => {
159 154 this.updateDeviceProfileValue(value);
160 155 });
161   - this.lwm2mDeviceConfigFormGroup.valueChanges.pipe(
162   - takeUntil(this.destroy$)
163   - ).subscribe(() => {
164   - this.updateModel();
165   - });
166 156 this.sortFunction = this.sortObjectKeyPathJson;
167 157 }
168 158
... ... @@ -182,10 +172,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
182 172 this.disabled = isDisabled;
183 173 if (isDisabled) {
184 174 this.lwm2mDeviceProfileFormGroup.disable({emitEvent: false});
185   - this.lwm2mDeviceConfigFormGroup.disable({emitEvent: false});
186 175 } else {
187 176 this.lwm2mDeviceProfileFormGroup.enable({emitEvent: false});
188   - this.lwm2mDeviceConfigFormGroup.enable({emitEvent: false});
189 177 this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.powerMode').updateValueAndValidity({onlySelf: true});
190 178 this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').updateValueAndValidity({onlySelf: true});
191 179 this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').updateValueAndValidity({onlySelf: true});
... ... @@ -196,9 +184,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
196 184 if (isDefinedAndNotNull(value) && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) {
197 185 this.configurationValue = value;
198 186 const defaultFormSettings = !(value.observeAttr.attribute.length && value.observeAttr.telemetry.length);
199   - this.lwm2mDeviceConfigFormGroup.patchValue({
200   - configurationJson: this.configurationValue
201   - }, {emitEvent: defaultFormSettings});
202 187 if (defaultFormSettings) {
203 188 await this.defaultProfileConfig();
204 189 }
... ... @@ -227,9 +212,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
227 212
228 213 this.configurationValue.bootstrap.bootstrapServer = bootstrap;
229 214 this.configurationValue.bootstrap.lwm2mServer = lwm2m;
230   - this.lwm2mDeviceConfigFormGroup.patchValue({
231   - configurationJson: this.configurationValue
232   - }, {emitEvent: false});
233 215 this.lwm2mDeviceProfileFormGroup.patchValue({
234 216 bootstrap: this.configurationValue.bootstrap
235 217 }, {emitEvent: false});
... ... @@ -265,6 +247,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
265 247 swUpdateResource: this.configurationValue.clientLwM2mSettings.swUpdateResource || '',
266 248 powerMode: this.configurationValue.clientLwM2mSettings.powerMode || PowerMode.DRX,
267 249 edrxCycle: this.configurationValue.clientLwM2mSettings.edrxCycle || 0,
  250 + pagingTransmissionWindow: this.configurationValue.clientLwM2mSettings.pagingTransmissionWindow || 0,
  251 + psmActivityTimer: this.configurationValue.clientLwM2mSettings.psmActivityTimer || 0,
268 252 compositeOperationsSupport: this.configurationValue.clientLwM2mSettings.compositeOperationsSupport || false
269 253 }
270 254 },
... ... @@ -277,9 +261,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
277 261 }
278 262
279 263 private updateModel = (): void => {
280   - let configuration: DeviceProfileTransportConfiguration = null;
281   - if (this.lwm2mDeviceConfigFormGroup.valid && this.lwm2mDeviceProfileFormGroup.valid) {
282   - configuration = this.lwm2mDeviceConfigFormGroup.value.configurationJson;
  264 + let configuration: Lwm2mProfileConfigModels = null;
  265 + if (this.lwm2mDeviceProfileFormGroup.valid) {
  266 + configuration = this.configurationValue;
283 267 }
284 268 this.propagateChange(configuration);
285 269 }
... ... @@ -299,7 +283,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
299 283 this.configurationValue.bootstrap.lwm2mServer = config.bootstrap.lwm2mServer;
300 284 this.configurationValue.bootstrap.servers = config.bootstrap.servers;
301 285 this.configurationValue.clientLwM2mSettings = config.clientLwM2mSettings;
302   - this.upDateJsonAllConfig();
303 286 this.updateModel();
304 287 }
305 288
... ... @@ -327,7 +310,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
327 310 }
328 311 if (isDefinedAndNotNull(keyNameJson)) {
329 312 this.configurationValue.observeAttr.keyName = this.validateKeyNameObjects(keyNameJson, attributeArray, telemetryArray);
330   - this.upDateJsonAllConfig();
331 313 this.updateKeyNameObjects(objectLwM2MS);
332 314 }
333 315 }
... ... @@ -513,12 +495,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
513 495 return (objectsIds.size > 0) ? Array.from(objectsIds) : [];
514 496 }
515 497
516   - private upDateJsonAllConfig = (): void => {
517   - this.lwm2mDeviceConfigFormGroup.patchValue({
518   - configurationJson: this.configurationValue
519   - }, {emitEvent: false});
520   - }
521   -
522 498 addObjectsList = (value: ObjectLwM2M[]): void => {
523 499 this.updateObserveAttrTelemetryObjectFormGroup(value);
524 500 }
... ... @@ -536,7 +512,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
536 512 this.removeKeyNameFromJson(value.keyId);
537 513 this.removeAttributesFromJson(value.keyId);
538 514 this.updateObserveAttrTelemetryObjectFormGroup(objectsOld);
539   - this.upDateJsonAllConfig();
540 515 }
541 516
542 517 private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, keyId: string): void => {
... ...
... ... @@ -59,9 +59,10 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot
59 59
60 60 this.config.columns.push(
61 61 new DateEntityTableColumn<OtaPackageInfo>('createdTime', 'common.created-time', this.datePipe, '150px'),
62   - new EntityTableColumn<OtaPackageInfo>('title', 'ota-update.title', '20%'),
63   - new EntityTableColumn<OtaPackageInfo>('version', 'ota-update.version', '20%'),
64   - new EntityTableColumn<OtaPackageInfo>('type', 'ota-update.package-type', '20%', entity => {
  62 + new EntityTableColumn<OtaPackageInfo>('title', 'ota-update.title', '15%'),
  63 + new EntityTableColumn<OtaPackageInfo>('version', 'ota-update.version', '15%'),
  64 + new EntityTableColumn<OtaPackageInfo>('tag', 'ota-update.version-tag', '15%'),
  65 + new EntityTableColumn<OtaPackageInfo>('type', 'ota-update.package-type', '15%', entity => {
65 66 return this.translate.instant(OtaUpdateTypeTranslationMap.get(entity.type));
66 67 }),
67 68 new EntityTableColumn<OtaPackageInfo>('url', 'ota-update.direct-url', '20%', entity => {
... ...
... ... @@ -74,6 +74,11 @@
74 74 </mat-error>
75 75 </mat-form-field>
76 76 </div>
  77 + <mat-form-field class="mat-block" fxFlex style="margin-bottom: 8px">
  78 + <mat-label translate>ota-update.version-tag</mat-label>
  79 + <input matInput formControlName="tag" type="text" [readonly]="!isAdd">
  80 + <mat-hint *ngIf="isAdd" translate>ota-update.version-tag-hint</mat-hint>
  81 + </mat-form-field>
77 82 <tb-device-profile-autocomplete
78 83 formControlName="deviceProfileId"
79 84 required
... ... @@ -94,8 +99,8 @@
94 99 <section *ngIf="isAdd">
95 100 <div class="mat-caption" style="margin: -8px 0 8px;" translate>ota-update.warning-after-save-no-edit</div>
96 101 <mat-radio-group formControlName="isURL" fxLayoutGap="16px">
97   - <mat-radio-button [value]="false">Upload binary file</mat-radio-button>
98   - <mat-radio-button [value]="true">Use external URL</mat-radio-button>
  102 + <mat-radio-button [value]="false">{{ "ota-update.upload-binary-file" | translate }}</mat-radio-button>
  103 + <mat-radio-button [value]="true">{{ "ota-update.use-external-url" | translate }}</mat-radio-button>
99 104 </mat-radio-group>
100 105 </section>
101 106 <section *ngIf="!entityForm.get('isURL').value">
... ...
... ... @@ -15,7 +15,7 @@
15 15 ///
16 16
17 17 import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
18   -import { Subject } from 'rxjs';
  18 +import { combineLatest, Subject } from 'rxjs';
19 19 import { Store } from '@ngrx/store';
20 20 import { AppState } from '@core/core.state';
21 21 import { TranslateService } from '@ngx-translate/core';
... ... @@ -30,7 +30,7 @@ import {
30 30 OtaUpdateTypeTranslationMap
31 31 } from '@shared/models/ota-package.models';
32 32 import { ActionNotificationShow } from '@core/notification/notification.actions';
33   -import { filter, takeUntil } from 'rxjs/operators';
  33 +import { filter, startWith, takeUntil } from 'rxjs/operators';
34 34 import { isNotEmptyStr } from '@core/utils';
35 35
36 36 @Component({
... ... @@ -56,22 +56,33 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
56 56
57 57 ngOnInit() {
58 58 super.ngOnInit();
59   - this.entityForm.get('isURL').valueChanges.pipe(
60   - filter(() => this.isAdd),
61   - takeUntil(this.destroy$)
62   - ).subscribe((isURL) => {
63   - if (isURL === false) {
64   - this.entityForm.get('url').clearValidators();
65   - this.entityForm.get('file').setValidators(Validators.required);
66   - this.entityForm.get('url').updateValueAndValidity({emitEvent: false});
67   - this.entityForm.get('file').updateValueAndValidity({emitEvent: false});
68   - } else {
69   - this.entityForm.get('file').clearValidators();
70   - this.entityForm.get('url').setValidators([Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]);
71   - this.entityForm.get('file').updateValueAndValidity({emitEvent: false});
72   - this.entityForm.get('url').updateValueAndValidity({emitEvent: false});
73   - }
74   - });
  59 + if (this.isAdd) {
  60 + this.entityForm.get('isURL').valueChanges.pipe(
  61 + takeUntil(this.destroy$)
  62 + ).subscribe((isURL) => {
  63 + if (isURL === false) {
  64 + this.entityForm.get('url').clearValidators();
  65 + this.entityForm.get('file').setValidators(Validators.required);
  66 + this.entityForm.get('url').updateValueAndValidity({emitEvent: false});
  67 + this.entityForm.get('file').updateValueAndValidity({emitEvent: false});
  68 + } else {
  69 + this.entityForm.get('file').clearValidators();
  70 + this.entityForm.get('url').setValidators([Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]);
  71 + this.entityForm.get('file').updateValueAndValidity({emitEvent: false});
  72 + this.entityForm.get('url').updateValueAndValidity({emitEvent: false});
  73 + }
  74 + });
  75 + combineLatest([
  76 + this.entityForm.get('title').valueChanges.pipe(startWith('')),
  77 + this.entityForm.get('version').valueChanges.pipe(startWith(''))
  78 + ]).pipe(
  79 + filter(() => this.entityForm.get('tag').pristine),
  80 + takeUntil(this.destroy$)
  81 + ).subscribe(([title, version]) => {
  82 + const tag = (`${title} ${version}`).trim();
  83 + this.entityForm.get('tag').patchValue(tag);
  84 + });
  85 + }
75 86 }
76 87
77 88 ngOnDestroy() {
... ... @@ -92,6 +103,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
92 103 const form = this.fb.group({
93 104 title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
94 105 version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]],
  106 + tag: [entity ? entity.tag : '', [Validators.maxLength(255)]],
95 107 type: [entity?.type ? entity.type : OtaUpdateType.FIRMWARE, Validators.required],
96 108 deviceProfileId: [entity ? entity.deviceProfileId : null, Validators.required],
97 109 checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256],
... ... @@ -119,6 +131,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
119 131 this.entityForm.patchValue({
120 132 title: entity.title,
121 133 version: entity.version,
  134 + tag: entity.tag,
122 135 type: entity.type,
123 136 deviceProfileId: entity.deviceProfileId,
124 137 checksumAlgorithm: entity.checksumAlgorithm,
... ...
... ... @@ -91,6 +91,7 @@ export interface OtaPackageInfo extends BaseData<OtaPackageId> {
91 91 deviceProfileId?: DeviceProfileId;
92 92 title?: string;
93 93 version?: string;
  94 + tag?: string;
94 95 hasData?: boolean;
95 96 url?: string;
96 97 fileName: string;
... ...
... ... @@ -1236,14 +1236,12 @@
1236 1236 "object-list": "SEznam objektů",
1237 1237 "object-list-empty": "Žádné objekty nebyly vybrány.",
1238 1238 "no-objects-matching": "Žádné objekty odpovídající '{{object}}' nebyly nalezeny.",
1239   - "valid-id-instance-no-min": "Instance číslo '{{instance}}' nebyla validována. Mininimální hodnota='{{min}}'",
1240   - "valid-id-instance-no-max": "Instance číslo '{{instance}}' nebyla validována. Maximální hodnota='{{max}}'",
1241   - "valid-id-instance": "Instance číslo '{{instance}}' nebyla validována. { count, plural, 1 {Maximální hodnota='{{max}}'} 2 {Minimální hodnota='{{min}}'} other {Musí být pouze číslo} }",
1242 1239 "model-tab": "LWM2M model",
1243 1240 "add-new-instances": "Přidat nové instance",
1244 1241 "instances-list": "Seznam instancí",
1245   - "instances-input": "Vstupní hodnota Id instance",
1246   - "instances-input-holder": "Vstupní číslo instance...",
  1242 + "instances-list-required": "Seznam instancí je povinný",
  1243 + "instance-id-pattern": "Instance číslo musí být kladné číslo.",
  1244 + "instance-id-max": "Maximální instance číslo hodnota {{max}}.",
1247 1245 "instance": "Instance",
1248 1246 "resource-label": "Název zdroje #ID",
1249 1247 "observe-label": "Pozorování",
... ... @@ -1266,7 +1264,6 @@
1266 1264 "view-attribute": "Zobrazit atribut",
1267 1265 "remove-attribute": "Odebrat atribut",
1268 1266 "mode": "Režim konfigurace bezpečnosti",
1269   - "pattern_hex_dec": "{ count, plural, 0 {musí být v hexadecimálním formátu} other {musí být # znaků} }",
1270 1267 "servers": "Servery",
1271 1268 "short-id": "Krátké ID",
1272 1269 "short-id-required": "Krátké ID je povinné.",
... ... @@ -1316,8 +1313,8 @@
1316 1313 "others-tab": "Ostatní nastavení",
1317 1314 "client-strategy": "Strategie klienta při připojování",
1318 1315 "client-strategy-label": "Strategie",
1319   - "client-strategy-connect": "{ count, plural, 1 {1: Klientovi je odeslán pouze observe požadavek po úvodním spojení} other {2: Načti všechny zdroje a observer požadavky na klienta po registraci} }",
1320   - "client-strategy-tip": "{ count, plural, 1 {Strategie 1: Po úvodním spojení LWM2M klienta, server odešle požadavek Observe zdrojů klientovi, přičemž tyto zdroje existující na straně LWM2M klienta jsou v profilu zařízení označeny jako pozorování.} other {Strategie 2: Po registraci, je klientovi odeslán požadavek na načtení hodnotu všech zdrojů všech objektů, které LWM2M klient má,\n poté: server odešle požadavek observe zdrojů klientovi, přičemž tyto zdroje existující na straně klienta, jsou v profilu zařízení označeny jako pozorování.} }",
  1316 + "client-strategy-only-observe": "Klientovi je odeslán pouze observe požadavek po úvodním spojení",
  1317 + "client-strategy-read-all": "Načti všechny zdroje a observer požadavky na klienta po registraci",
1321 1318 "fw-update": "Aktualizace firmware",
1322 1319 "fw-update-strategy": "Strategie aktualizace firmware",
1323 1320 "fw-update-strategy-data": "Odeslat (push) aktualizaci firmware jako binární soubor pomocí Object 19 a Resource 0 (Data)",
... ...
... ... @@ -1273,7 +1273,6 @@
1273 1273 "view-attribute": "View attribute",
1274 1274 "remove-attribute": "Remove attribute",
1275 1275 "mode": "Security config mode",
1276   - "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }",
1277 1276 "servers": "Servers",
1278 1277 "short-id": "Short ID",
1279 1278 "short-id-required": "Short ID is required.",
... ... @@ -1323,8 +1322,8 @@
1323 1322 "others-tab": "Other settings",
1324 1323 "client-strategy": "Client strategy when connecting",
1325 1324 "client-strategy-label": "Strategy",
1326   - "client-strategy-connect": "{ count, plural, 1 {1: Only Observe Request to the client after the initial connection} other {2: Read All Resources & Observe Request to the client after registration} }",
1327   - "client-strategy-tip": "{ count, plural, 1 {Strategy 1: After the initial connection of the LWM2M Client, the server sends Observe resources Request to the client, those resources that are marked as observation in the Device profile and which exist on the LWM2M client.} other {Strategy 2: After the registration, request the client to read all the resource values for all objects that the LWM2M client has,\n then execute: the server sends Observe resources Request to the client, those resources that are marked as observation in the Device profile and which exist on the LWM2M client.} }",
  1325 + "client-strategy-only-observe": "Only Observe Request to the client after the initial connection",
  1326 + "client-strategy-read-all": "Read All Resources & Observe Request to the client after registration",
1328 1327 "fw-update": "Firmware update",
1329 1328 "fw-update-strategy": "Firmware update strategy",
1330 1329 "fw-update-strategy-data": "Push firmware update as binary file using Object 19 and Resource 0 (Data)",
... ... @@ -2342,8 +2341,12 @@
2342 2341 "firmware": "Firmware",
2343 2342 "software": "Software"
2344 2343 },
  2344 + "upload-binary-file": "Upload binary file",
  2345 + "use-external-url": "Use external URL",
2345 2346 "version": "Version",
2346 2347 "version-required": "Version is required.",
  2348 + "version-tag": "Version Tag",
  2349 + "version-tag-hint": "Custom tag should match the package version reported by your device.",
2347 2350 "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type."
2348 2351 },
2349 2352 "position": {
... ...