Browse Source

Merge remote-tracking branch 'upstream/master' into edge-fix-concurrent-async-issue

pull/4957/head
Volodymyr Babak 5 years ago
parent
commit
bf4ae8fd16
  1. 18
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 1
      application/src/main/data/upgrade/3.2.2/schema_update.sql
  3. 24
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  4. 1
      application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
  5. 9
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  6. 6
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  7. 2
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
  8. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageKey.java
  9. 11
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  10. 2
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java
  11. 6
      common/queue/src/main/proto/queue.proto
  12. 3
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  13. 25
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java
  14. 8
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/TbCoapClientState.java
  15. 3
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  16. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  17. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java
  18. 20
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java
  19. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java
  20. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java
  21. 3
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
  22. 3
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java
  23. 3
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java
  24. 3
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  25. 8
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  26. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  27. 74
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java
  28. 73
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java
  29. 5
      dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
  30. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java
  31. 1
      dao/src/main/resources/sql/schema-entities-hsql.sql
  32. 1
      dao/src/main/resources/sql/schema-entities.sql
  33. 3
      ui-ngx/angular.json
  34. 1
      ui-ngx/package.json
  35. 16
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html
  36. 37
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts
  37. 21
      ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.html
  38. 129
      ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts
  39. 7
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  40. 7
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts
  41. 9
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html
  42. 49
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts
  43. 1
      ui-ngx/src/app/shared/models/ota-package.models.ts
  44. 13
      ui-ngx/src/assets/locale/locale.constant-cs_CZ.json
  45. 9
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  46. 52
      ui-ngx/yarn.lock

18
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

1
application/src/main/data/upgrade/3.2.2/schema_update.sql

@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
type varchar(32) NOT NULL, type varchar(32) NOT NULL,
title varchar(255) NOT NULL, title varchar(255) NOT NULL,
version varchar(255) NOT NULL, version varchar(255) NOT NULL,
tag varchar(255),
url varchar(255), url varchar(255),
file_name varchar(255), file_name varchar(255),
content_type varchar(255), content_type varchar(255),

24
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -59,6 +59,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
@ -200,7 +201,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
syncSessionSet.add(key); syncSessionSet.add(key);
} }
}); });
log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions); log.trace("Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions);
syncSessionSet.forEach(rpcSubscriptions::remove); syncSessionSet.forEach(rpcSubscriptions::remove);
} }
@ -316,7 +317,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
.setOneway(request.isOneway()) .setOneway(request.isOneway())
.setPersisted(request.isPersisted()) .setPersisted(request.isPersisted())
.build(); .build();
sendToTransport(rpcRequest, sessionId, nodeId); sendToTransport(rpcRequest, sessionId, nodeId);
}; };
} }
@ -353,9 +353,26 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (msg.hasPersistedRpcResponseMsg()) { if (msg.hasPersistedRpcResponseMsg()) {
processPersistedRpcResponses(context, sessionInfo, msg.getPersistedRpcResponseMsg()); processPersistedRpcResponses(context, sessionInfo, msg.getPersistedRpcResponseMsg());
} }
if (msg.hasUplinkNotificationMsg()) {
processUplinkNotificationMsg(context, sessionInfo, msg.getUplinkNotificationMsg());
}
callback.onSuccess(); callback.onSuccess();
} }
private void processUplinkNotificationMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg uplinkNotificationMsg) {
String nodeId = sessionInfo.getNodeId();
sessions.entrySet().stream()
.filter(kv -> kv.getValue().getSessionInfo().getNodeId().equals(nodeId) && (kv.getValue().isSubscribedToAttributes() || kv.getValue().isSubscribedToRPC()))
.forEach(kv -> {
ToTransportMsg msg = ToTransportMsg.newBuilder()
.setSessionIdMSB(kv.getKey().getMostSignificantBits())
.setSessionIdLSB(kv.getKey().getLeastSignificantBits())
.setUplinkNotificationMsg(uplinkNotificationMsg)
.build();
systemContext.getTbCoreToTransportService().process(kv.getValue().getSessionInfo().getNodeId(), msg);
});
}
private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) { private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) {
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs()); systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
@ -597,7 +614,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
void processCredentialsUpdate(TbActorMsg msg) { void processCredentialsUpdate(TbActorMsg msg) {
if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
log.info("1) LwM2Mtype: ");
sessions.forEach((k, v) -> { sessions.forEach((k, v) -> {
notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials()); notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
}); });
@ -614,7 +630,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!"); notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!");
} }
private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) { private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) {
SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto
.newBuilder() .newBuilder()
@ -628,7 +643,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) { void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) {
log.info("2) LwM2Mtype: ");
ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder(); ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder();
notification.addCredentialsId(deviceCredentials.getCredentialsId()); notification.addCredentialsId(deviceCredentials.getCredentialsId());
notification.addCredentialsValue(deviceCredentials.getCredentialsValue()); notification.addCredentialsValue(deviceCredentials.getCredentialsValue());

1
application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java

@ -146,6 +146,7 @@ public class OtaPackageController extends BaseController {
otaPackage.setType(info.getType()); otaPackage.setType(info.getType());
otaPackage.setTitle(info.getTitle()); otaPackage.setTitle(info.getTitle());
otaPackage.setVersion(info.getVersion()); otaPackage.setVersion(info.getVersion());
otaPackage.setTag(info.getTag());
otaPackage.setAdditionalInfo(info.getAdditionalInfo()); otaPackage.setAdditionalInfo(info.getAdditionalInfo());
ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase());

9
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java

@ -64,6 +64,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM_ALGORITHM; import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM_ALGORITHM;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE; import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.TAG;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE; import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS; import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL; import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL;
@ -246,6 +247,11 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
List<TsKvEntry> telemetry = new ArrayList<>(); List<TsKvEntry> telemetry = new ArrayList<>();
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle())));
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion())));
if (StringUtils.isNotEmpty(firmware.getTag())) {
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TAG), firmware.getTag())));
}
telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts))); telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts)));
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name())));
@ -289,6 +295,9 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
List<AttributeKvEntry> attributes = new ArrayList<>(); List<AttributeKvEntry> attributes = new ArrayList<>();
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
if (StringUtils.isNotEmpty(otaPackage.getTag())) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TAG), otaPackage.getTag())));
}
if (otaPackage.hasUrl()) { if (otaPackage.hasUrl()) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
List<String> attrToRemove = new ArrayList<>(); List<String> attrToRemove = new ArrayList<>();

6
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -36,6 +36,12 @@ public class DataConstants {
public static final String ALARM_CONDITION_REPEATS = "alarmConditionRepeats"; public static final String ALARM_CONDITION_REPEATS = "alarmConditionRepeats";
public static final String ALARM_CONDITION_DURATION = "alarmConditionDuration"; public static final String ALARM_CONDITION_DURATION = "alarmConditionDuration";
public static final String PERSISTENT = "persistent"; public static final String PERSISTENT = "persistent";
public static final String COAP_TRANSPORT_NAME = "COAP";
public static final String LWM2M_TRANSPORT_NAME = "LWM2M";
public static final String MQTT_TRANSPORT_NAME = "MQTT";
public static final String HTTP_TRANSPORT_NAME = "HTTP";
public static final String SNMP_TRANSPORT_NAME = "SNMP";
public static final String[] allScopes() { public static final String[] allScopes() {
return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE}; return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE};

2
common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java

@ -37,6 +37,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
private OtaPackageType type; private OtaPackageType type;
private String title; private String title;
private String version; private String version;
private String tag;
private String url; private String url;
private boolean hasData; private boolean hasData;
private String fileName; private String fileName;
@ -61,6 +62,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
this.type = otaPackageInfo.getType(); this.type = otaPackageInfo.getType();
this.title = otaPackageInfo.getTitle(); this.title = otaPackageInfo.getTitle();
this.version = otaPackageInfo.getVersion(); this.version = otaPackageInfo.getVersion();
this.tag = otaPackageInfo.getTag();
this.url = otaPackageInfo.getUrl(); this.url = otaPackageInfo.getUrl();
this.hasData = otaPackageInfo.isHasData(); this.hasData = otaPackageInfo.isHasData();
this.fileName = otaPackageInfo.getFileName(); this.fileName = otaPackageInfo.getFileName();

2
common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageKey.java

@ -19,7 +19,7 @@ import lombok.Getter;
public enum OtaPackageKey { public enum OtaPackageKey {
TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"), URL("url"); TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"), URL("url"), TAG("tag");
@Getter @Getter
private final String value; private final String value;

11
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -73,6 +73,7 @@ public class HashPartitionService implements PartitionService {
private Map<String, TopicPartitionInfo> tbCoreNotificationTopics = new HashMap<>(); private Map<String, TopicPartitionInfo> tbCoreNotificationTopics = new HashMap<>();
private Map<String, TopicPartitionInfo> tbRuleEngineNotificationTopics = new HashMap<>(); private Map<String, TopicPartitionInfo> tbRuleEngineNotificationTopics = new HashMap<>();
private Map<String, List<ServiceInfo>> tbTransportServicesByType = new HashMap<>();
private List<ServiceInfo> currentOtherServices; private List<ServiceInfo> currentOtherServices;
private HashFunction hashFunction; private HashFunction hashFunction;
@ -127,6 +128,7 @@ public class HashPartitionService implements PartitionService {
@Override @Override
public synchronized void recalculatePartitions(ServiceInfo currentService, List<ServiceInfo> otherServices) { public synchronized void recalculatePartitions(ServiceInfo currentService, List<ServiceInfo> otherServices) {
tbTransportServicesByType.clear();
logServiceInfo(currentService); logServiceInfo(currentService);
otherServices.forEach(this::logServiceInfo); otherServices.forEach(this::logServiceInfo);
Map<ServiceQueueKey, List<ServiceInfo>> queueServicesMap = new HashMap<>(); Map<ServiceQueueKey, List<ServiceInfo>> queueServicesMap = new HashMap<>();
@ -229,6 +231,12 @@ public class HashPartitionService implements PartitionService {
return Math.abs(hash % partitions); return Math.abs(hash % partitions);
} }
@Override
public int countTransportsByType(String type) {
var list = tbTransportServicesByType.get(type);
return list == null ? 0 : list.size();
}
private Map<ServiceQueueKey, List<ServiceInfo>> getServiceKeyListMap(List<ServiceInfo> services) { private Map<ServiceQueueKey, List<ServiceInfo>> getServiceKeyListMap(List<ServiceInfo> services) {
final Map<ServiceQueueKey, List<ServiceInfo>> currentMap = new HashMap<>(); final Map<ServiceQueueKey, List<ServiceInfo>> currentMap = new HashMap<>();
services.forEach(serviceInfo -> { services.forEach(serviceInfo -> {
@ -332,6 +340,9 @@ public class HashPartitionService implements PartitionService {
queueServiceList.computeIfAbsent(serviceQueueKey, key -> new ArrayList<>()).add(instance); queueServiceList.computeIfAbsent(serviceQueueKey, key -> new ArrayList<>()).add(instance);
} }
} }
for (String transportType : instance.getTransportsList()) {
tbTransportServicesByType.computeIfAbsent(transportType, t -> new ArrayList<>()).add(instance);
}
} }
private ServiceInfo resolveByPartitionIdx(List<ServiceInfo> servers, Integer partitionIdx) { private ServiceInfo resolveByPartitionIdx(List<ServiceInfo> servers, Integer partitionIdx) {

2
common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java

@ -59,4 +59,6 @@ public interface PartitionService {
TopicPartitionInfo getNotificationsTopic(ServiceType serviceType, String serviceId); TopicPartitionInfo getNotificationsTopic(ServiceType serviceType, String serviceId);
int resolvePartitionIndex(UUID entityId, int partitions); int resolvePartitionIndex(UUID entityId, int partitions);
int countTransportsByType(String type);
} }

6
common/queue/src/main/proto/queue.proto

@ -341,6 +341,10 @@ message ToDeviceRpcResponseMsg {
string payload = 2; string payload = 2;
} }
message UplinkNotificationMsg {
int64 uplinkTs = 1;
}
message ToDevicePersistedRpcResponseMsg { message ToDevicePersistedRpcResponseMsg {
int32 requestId = 1; int32 requestId = 1;
int64 requestIdMSB = 2; int64 requestIdMSB = 2;
@ -453,6 +457,7 @@ message TransportToDeviceActorMsg {
ProvisionDeviceRequestMsg provisionDevice = 9; ProvisionDeviceRequestMsg provisionDevice = 9;
ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 10; ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 10;
SendPendingRPCMsg sendPendingRPC = 11; SendPendingRPCMsg sendPendingRPC = 11;
UplinkNotificationMsg uplinkNotificationMsg = 12;
} }
message TransportToRuleEngineMsg { message TransportToRuleEngineMsg {
@ -713,6 +718,7 @@ message ToTransportMsg {
ToTransportUpdateCredentialsProto toTransportUpdateCredentialsNotification = 11; ToTransportUpdateCredentialsProto toTransportUpdateCredentialsNotification = 11;
ResourceUpdateMsg resourceUpdateMsg = 12; ResourceUpdateMsg resourceUpdateMsg = 12;
ResourceDeleteMsg resourceDeleteMsg = 13; ResourceDeleteMsg resourceDeleteMsg = 13;
UplinkNotificationMsg uplinkNotificationMsg = 14;
} }
message UsageStatsKVProto{ message UsageStatsKVProto{

3
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java

@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.coapserver.CoapServerService; import org.thingsboard.server.coapserver.CoapServerService;
import org.thingsboard.server.coapserver.TbCoapServerComponent; import org.thingsboard.server.coapserver.TbCoapServerComponent;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource;
@ -72,6 +73,6 @@ public class CoapTransportService implements TbTransportService {
@Override @Override
public String getName() { public String getName() {
return "COAP"; return DataConstants.COAP_TRANSPORT_NAME;
} }
} }

25
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java

@ -18,14 +18,13 @@ package org.thingsboard.server.transport.coap.client;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.observe.ObserveRelation;
import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.CoapExchange;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.coapserver.CoapServerContext; import org.thingsboard.server.coapserver.CoapServerContext;
import org.thingsboard.server.coapserver.TbCoapServerComponent; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
@ -51,6 +50,7 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.transport.coap.CoapTransportContext; import org.thingsboard.server.transport.coap.CoapTransportContext;
import org.thingsboard.server.transport.coap.TbCoapMessageObserver; import org.thingsboard.server.transport.coap.TbCoapMessageObserver;
import org.thingsboard.server.transport.coap.TransportConfigurationContainer; import org.thingsboard.server.transport.coap.TransportConfigurationContainer;
@ -81,6 +81,7 @@ public class DefaultCoapClientContext implements CoapClientContext {
private final CoapTransportContext transportContext; private final CoapTransportContext transportContext;
private final TransportService transportService; private final TransportService transportService;
private final TransportDeviceProfileCache profileCache; private final TransportDeviceProfileCache profileCache;
private final PartitionService partitionService;
private final ConcurrentMap<DeviceId, TbCoapClientState> clients = new ConcurrentHashMap<>(); private final ConcurrentMap<DeviceId, TbCoapClientState> clients = new ConcurrentHashMap<>();
private final ConcurrentMap<String, TbCoapClientState> clientsByToken = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TbCoapClientState> clientsByToken = new ConcurrentHashMap<>();
@ -161,7 +162,7 @@ public class DefaultCoapClientContext implements CoapClientContext {
} }
} }
private void onUplink(TbCoapClientState client) { private void onUplink(TbCoapClientState client, boolean notifyOtherServers, long uplinkTs) {
PowerMode powerMode = client.getPowerMode(); PowerMode powerMode = client.getPowerMode();
PowerSavingConfiguration profileSettings = null; PowerSavingConfiguration profileSettings = null;
if (powerMode == null) { if (powerMode == null) {
@ -174,12 +175,12 @@ public class DefaultCoapClientContext implements CoapClientContext {
} }
} }
if (powerMode == null || PowerMode.DRX.equals(powerMode)) { if (powerMode == null || PowerMode.DRX.equals(powerMode)) {
client.updateLastUplinkTime(); client.updateLastUplinkTime(uplinkTs);
return; return;
} }
client.lock(); client.lock();
try { try {
long uplinkTime = client.updateLastUplinkTime(); long uplinkTime = client.updateLastUplinkTime(uplinkTs);
long timeout; long timeout;
if (PowerMode.PSM.equals(powerMode)) { if (PowerMode.PSM.equals(powerMode)) {
Long psmActivityTimer = client.getPsmActivityTimer(); Long psmActivityTimer = client.getPsmActivityTimer();
@ -214,6 +215,9 @@ public class DefaultCoapClientContext implements CoapClientContext {
return null; return null;
}, timeout, TimeUnit.MILLISECONDS); }, timeout, TimeUnit.MILLISECONDS);
client.setSleepTask(task); client.setSleepTask(task);
if (notifyOtherServers && partitionService.countTransportsByType(DataConstants.COAP_TRANSPORT_NAME) > 1) {
transportService.notifyAboutUplink(getNewSyncSession(client), TransportProtos.UplinkNotificationMsg.newBuilder().setUplinkTs(uplinkTime).build(), TransportServiceCallback.EMPTY);
}
} finally { } finally {
client.unlock(); client.unlock();
} }
@ -544,6 +548,11 @@ public class DefaultCoapClientContext implements CoapClientContext {
log.trace("[{}] Received server rpc response in the wrong session.", state.getSession()); log.trace("[{}] Received server rpc response in the wrong session.", state.getSession());
} }
@Override
public void onUplinkNotification(TransportProtos.UplinkNotificationMsg notificationMsg) {
awake(state, false, notificationMsg.getUplinkTs());
}
private void cancelObserveRelation(TbCoapObservationState attrs) { private void cancelObserveRelation(TbCoapObservationState attrs) {
if (attrs.getObserveRelation() != null) { if (attrs.getObserveRelation() != null) {
attrs.getObserveRelation().cancel(); attrs.getObserveRelation().cancel();
@ -562,7 +571,11 @@ public class DefaultCoapClientContext implements CoapClientContext {
@Override @Override
public boolean awake(TbCoapClientState client) { public boolean awake(TbCoapClientState client) {
onUplink(client); return awake(client, true, System.currentTimeMillis());
}
private boolean awake(TbCoapClientState client, boolean notifyOtherServers, long uplinkTs) {
onUplink(client, notifyOtherServers, uplinkTs);
boolean changed = compareAndSetSleepFlag(client, false); boolean changed = compareAndSetSleepFlag(client, false);
if (changed) { if (changed) {
log.debug("[{}] client is awake", client.getDeviceId()); log.debug("[{}] client is awake", client.getDeviceId());

8
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/TbCoapClientState.java

@ -97,9 +97,11 @@ public class TbCoapClientState {
lock.unlock(); lock.unlock();
} }
public long updateLastUplinkTime() { public long updateLastUplinkTime(long ts) {
this.lastUplinkTime = System.currentTimeMillis(); if (ts > lastUplinkTime) {
this.firstEdrxDownlink = true; this.lastUplinkTime = ts;
this.firstEdrxDownlink = true;
}
return lastUplinkTime; return lastUplinkTime;
} }

3
common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -436,7 +437,7 @@ public class DeviceApiController implements TbTransportService {
@Override @Override
public String getName() { public String getName() {
return "HTTP"; return DataConstants.HTTP_TRANSPORT_NAME;
} }
} }

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -28,6 +28,7 @@ import org.eclipse.leshan.server.californium.registration.CaliforniumRegistratio
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.DataConstants;
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.secure.TbLwM2MAuthorizer; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer;
@ -177,7 +178,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
@Override @Override
public String getName() { public String getName() {
return "LWM2M"; return DataConstants.LWM2M_TRANSPORT_NAME;
} }
} }

10
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java

@ -120,9 +120,11 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) { if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) {
String newFirmwareTitle = null; String newFirmwareTitle = null;
String newFirmwareVersion = null; String newFirmwareVersion = null;
String newFirmwareTag = null;
String newFirmwareUrl = null; String newFirmwareUrl = null;
String newSoftwareTitle = null; String newSoftwareTitle = null;
String newSoftwareVersion = null; String newSoftwareVersion = null;
String newSoftwareTag = null;
String newSoftwareUrl = null; String newSoftwareUrl = null;
List<TransportProtos.TsKvProto> otherAttributes = new ArrayList<>(); List<TransportProtos.TsKvProto> otherAttributes = new ArrayList<>();
for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) { for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) {
@ -131,12 +133,16 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
newFirmwareTitle = getStrValue(tsKvProto); newFirmwareTitle = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION.equals(attrName)) { } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION.equals(attrName)) {
newFirmwareVersion = getStrValue(tsKvProto); newFirmwareVersion = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.FIRMWARE_TAG.equals(attrName)) {
newFirmwareTag = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.FIRMWARE_URL.equals(attrName)) { } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_URL.equals(attrName)) {
newFirmwareUrl = getStrValue(tsKvProto); newFirmwareUrl = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE.equals(attrName)) { } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE.equals(attrName)) {
newSoftwareTitle = getStrValue(tsKvProto); newSoftwareTitle = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) { } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) {
newSoftwareVersion = getStrValue(tsKvProto); newSoftwareVersion = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TAG.equals(attrName)) {
newSoftwareTag = getStrValue(tsKvProto);
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) { } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) {
newSoftwareUrl = getStrValue(tsKvProto); newSoftwareUrl = getStrValue(tsKvProto);
}else { }else {
@ -144,10 +150,10 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
} }
} }
if (newFirmwareTitle != null || newFirmwareVersion != null) { if (newFirmwareTitle != null || newFirmwareVersion != null) {
otaUpdateService.onTargetFirmwareUpdate(lwM2MClient, newFirmwareTitle, newFirmwareVersion, Optional.ofNullable(newFirmwareUrl)); otaUpdateService.onTargetFirmwareUpdate(lwM2MClient, newFirmwareTitle, newFirmwareVersion, Optional.ofNullable(newFirmwareUrl), Optional.ofNullable(newFirmwareTag));
} }
if (newSoftwareTitle != null || newSoftwareVersion != null) { if (newSoftwareTitle != null || newSoftwareVersion != null) {
otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl)); otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag));
} }
if (!otherAttributes.isEmpty()) { if (!otherAttributes.isEmpty()) {
onAttributesUpdate(lwM2MClient, otherAttributes); onAttributesUpdate(lwM2MClient, otherAttributes);

20
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java

@ -85,9 +85,11 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
public static final String FIRMWARE_VERSION = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION); public static final String FIRMWARE_VERSION = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION);
public static final String FIRMWARE_TITLE = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE); public static final String FIRMWARE_TITLE = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE);
public static final String FIRMWARE_TAG = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TAG);
public static final String FIRMWARE_URL = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.URL); public static final String FIRMWARE_URL = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.URL);
public static final String SOFTWARE_VERSION = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION); public static final String SOFTWARE_VERSION = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION);
public static final String SOFTWARE_TITLE = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE); public static final String SOFTWARE_TITLE = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE);
public static final String SOFTWARE_TAG = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TAG);
public static final String SOFTWARE_URL = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.URL); public static final String SOFTWARE_URL = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.URL);
public static final String FIRMWARE_UPDATE_COAP_RESOURCE = "tbfw"; public static final String FIRMWARE_UPDATE_COAP_RESOURCE = "tbfw";
@ -165,6 +167,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
if (fwInfo.isSupported()) { if (fwInfo.isSupported()) {
attributesToFetch.add(FIRMWARE_TITLE); attributesToFetch.add(FIRMWARE_TITLE);
attributesToFetch.add(FIRMWARE_VERSION); attributesToFetch.add(FIRMWARE_VERSION);
attributesToFetch.add(FIRMWARE_TAG);
attributesToFetch.add(FIRMWARE_URL); attributesToFetch.add(FIRMWARE_URL);
} }
@ -172,6 +175,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
if (swInfo.isSupported()) { if (swInfo.isSupported()) {
attributesToFetch.add(SOFTWARE_TITLE); attributesToFetch.add(SOFTWARE_TITLE);
attributesToFetch.add(SOFTWARE_VERSION); attributesToFetch.add(SOFTWARE_VERSION);
attributesToFetch.add(SOFTWARE_TAG);
attributesToFetch.add(SOFTWARE_URL); attributesToFetch.add(SOFTWARE_URL);
} }
@ -186,17 +190,19 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
if (fwInfo.isSupported()) { if (fwInfo.isSupported()) {
Optional<String> newFwTitle = getAttributeValue(attrs, FIRMWARE_TITLE); Optional<String> newFwTitle = getAttributeValue(attrs, FIRMWARE_TITLE);
Optional<String> newFwVersion = getAttributeValue(attrs, FIRMWARE_VERSION); Optional<String> newFwVersion = getAttributeValue(attrs, FIRMWARE_VERSION);
Optional<String> newFwTag = getAttributeValue(attrs, FIRMWARE_TAG);
Optional<String> newFwUrl = getAttributeValue(attrs, FIRMWARE_URL); Optional<String> newFwUrl = getAttributeValue(attrs, FIRMWARE_URL);
if (newFwTitle.isPresent() && newFwVersion.isPresent()) { if (newFwTitle.isPresent() && newFwVersion.isPresent()) {
onTargetFirmwareUpdate(client, newFwTitle.get(), newFwVersion.get(), newFwUrl); onTargetFirmwareUpdate(client, newFwTitle.get(), newFwVersion.get(), newFwUrl, newFwTag);
} }
} }
if (swInfo.isSupported()) { if (swInfo.isSupported()) {
Optional<String> newSwTitle = getAttributeValue(attrs, SOFTWARE_TITLE); Optional<String> newSwTitle = getAttributeValue(attrs, SOFTWARE_TITLE);
Optional<String> newSwVersion = getAttributeValue(attrs, SOFTWARE_VERSION); Optional<String> newSwVersion = getAttributeValue(attrs, SOFTWARE_VERSION);
Optional<String> newSwTag = getAttributeValue(attrs, SOFTWARE_TAG);
Optional<String> newSwUrl = getAttributeValue(attrs, SOFTWARE_URL); Optional<String> newSwUrl = getAttributeValue(attrs, SOFTWARE_URL);
if (newSwTitle.isPresent() && newSwVersion.isPresent()) { if (newSwTitle.isPresent() && newSwVersion.isPresent()) {
onTargetSoftwareUpdate(client, newSwTitle.get(), newSwVersion.get(), newSwUrl); onTargetSoftwareUpdate(client, newSwTitle.get(), newSwVersion.get(), newSwUrl, newSwTag);
} }
} }
}, throwable -> { }, throwable -> {
@ -216,9 +222,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
} }
@Override @Override
public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional<String> newFirmwareUrl) { public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional<String> newFirmwareUrl, Optional<String> newFirmwareTag) {
LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client);
fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl); fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl, newFirmwareTag);
update(fwInfo); update(fwInfo);
startFirmwareUpdateIfNeeded(client, fwInfo); startFirmwareUpdateIfNeeded(client, fwInfo);
} }
@ -354,9 +360,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
} }
@Override @Override
public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion, Optional<String> newFirmwareUrl) { public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion, Optional<String> newSoftwareUrl, Optional<String> newSoftwareTag) {
LwM2MClientSwOtaInfo fwInfo = getOrInitSwInfo(client); LwM2MClientSwOtaInfo fwInfo = getOrInitSwInfo(client);
fwInfo.updateTarget(newSoftwareTitle, newSoftwareVersion, newFirmwareUrl); fwInfo.updateTarget(newSoftwareTitle, newSoftwareVersion, newSoftwareUrl, newSoftwareTag);
update(fwInfo); update(fwInfo);
startSoftwareUpdateIfNeeded(client, fwInfo); startSoftwareUpdateIfNeeded(client, fwInfo);
} }
@ -368,7 +374,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!"); sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!");
} else if (fwInfo.isUpdateRequired()) { } else if (fwInfo.isUpdateRequired()) {
if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) { if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) {
log.debug("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl()); log.debug("[{}] Starting update to [{}{}][] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl());
startUpdateUsingUrl(client, FW_URL_ID, fwInfo.getTargetUrl()); startUpdateUsingUrl(client, FW_URL_ID, fwInfo.getTargetUrl());
} else { } else {
log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion()); log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion());

13
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java

@ -32,6 +32,7 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
protected String targetName; protected String targetName;
protected String targetVersion; protected String targetVersion;
protected String targetTag;
protected String targetUrl; protected String targetUrl;
//TODO: use value from device if applicable; //TODO: use value from device if applicable;
@ -52,10 +53,11 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
this.strategy = strategy; this.strategy = strategy;
} }
public void updateTarget(String targetName, String targetVersion, Optional<String> newTargetUrl) { public void updateTarget(String targetName, String targetVersion, Optional<String> newTargetUrl, Optional<String> newTargetTag) {
this.targetName = targetName; this.targetName = targetName;
this.targetVersion = targetVersion; this.targetVersion = targetVersion;
this.targetUrl = newTargetUrl.orElse(null); this.targetUrl = newTargetUrl.orElse(null);
this.targetTag = newTargetTag.orElse(null);
} }
@JsonIgnore @JsonIgnore
@ -64,13 +66,18 @@ public abstract class LwM2MClientOtaInfo<Strategy, State, Result> {
return false; return false;
} else { } else {
String targetPackageId = getPackageId(targetName, targetVersion); String targetPackageId = getPackageId(targetName, targetVersion);
String currentPackageIdUsingObject5 = getPackageId(currentName, currentVersion); String currentPackageId = getPackageId(currentName, currentVersion);
if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) { if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) {
return false; return false;
} else { } else {
if (targetPackageId.equals(currentPackageIdUsingObject5)) { if (targetPackageId.equals(currentPackageId)) {
return false;
} else if (StringUtils.isNotEmpty(targetTag) && 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 false;
}
return !currentVersion3.contains(targetPackageId); return !currentVersion3.contains(targetPackageId);
} else { } else {
return true; return true;

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java

@ -26,9 +26,9 @@ public interface LwM2MOtaUpdateService {
void forceFirmwareUpdate(LwM2mClient client); void forceFirmwareUpdate(LwM2mClient client);
void onTargetFirmwareUpdate(LwM2mClient client, String newFwTitle, String newFwVersion, Optional<String> newFwUrl); void onTargetFirmwareUpdate(LwM2mClient client, String newFwTitle, String newFwVersion, Optional<String> newFwUrl, Optional<String> newFwTag);
void onTargetSoftwareUpdate(LwM2mClient client, String newSwTitle, String newSwVersion, Optional<String> newSwUrl); void onTargetSoftwareUpdate(LwM2mClient client, String newSwTitle, String newSwVersion, Optional<String> newSwUrl, Optional<String> newSwTag);
void onCurrentFirmwareNameUpdate(LwM2mClient client, String name); void onCurrentFirmwareNameUpdate(LwM2mClient client, String name);

3
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java

@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.TbTransportService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -114,6 +115,6 @@ public class MqttTransportService implements TbTransportService {
@Override @Override
public String getName() { public String getName() {
return "MQTT"; return DataConstants.MQTT_TRANSPORT_NAME;
} }
} }

3
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java

@ -35,6 +35,7 @@ import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.TbTransportService;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec;
@ -300,7 +301,7 @@ public class SnmpTransportService implements TbTransportService {
@Override @Override
public String getName() { public String getName() {
return "SNMP"; return DataConstants.SNMP_TRANSPORT_NAME;
} }
@PreDestroy @PreDestroy

3
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java

@ -25,6 +25,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotifica
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
import org.thingsboard.server.gen.transport.TransportProtos.UplinkNotificationMsg;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -44,6 +45,8 @@ public interface SessionMsgListener {
void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse); void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse);
default void onUplinkNotification(UplinkNotificationMsg notificationMsg){};
default void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto toTransportUpdateCredentials){} default void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto toTransportUpdateCredentials){}
default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) {} default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) {}

3
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.SessionMetaData;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg;
@ -128,4 +129,6 @@ public interface TransportService {
void deregisterSession(SessionInfoProto sessionInfo); void deregisterSession(SessionInfoProto sessionInfo);
void log(SessionInfoProto sessionInfo, String msg); void log(SessionInfoProto sessionInfo, String msg);
void notifyAboutUplink(SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg build, TransportServiceCallback<Void> empty);
} }

8
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -571,6 +571,14 @@ public class DefaultTransportService implements TransportService {
} }
} }
@Override
public void notifyAboutUplink(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg msg, TransportServiceCallback<Void> callback) {
if (checkLimits(sessionInfo, msg, callback)) {
reportActivityInternal(sessionInfo);
sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setUplinkNotificationMsg(msg).build(), callback);
}
}
@Override @Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcRequestMsg msg, boolean isFailedRpc, TransportServiceCallback<Void> callback) { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcRequestMsg msg, boolean isFailedRpc, TransportServiceCallback<Void> callback) {
if (msg.getPersisted()) { if (msg.getPersisted()) {

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -499,6 +499,7 @@ public class ModelConstants {
public static final String OTA_PACKAGE_TYPE_COLUMN = "type"; public static final String OTA_PACKAGE_TYPE_COLUMN = "type";
public static final String OTA_PACKAGE_TILE_COLUMN = TITLE_PROPERTY; public static final String OTA_PACKAGE_TILE_COLUMN = TITLE_PROPERTY;
public static final String OTA_PACKAGE_VERSION_COLUMN = "version"; public static final String OTA_PACKAGE_VERSION_COLUMN = "version";
public static final String OTA_PACKAGE_TAG_COLUMN = "tag";
public static final String OTA_PACKAGE_URL_COLUMN = "url"; public static final String OTA_PACKAGE_URL_COLUMN = "url";
public static final String OTA_PACKAGE_FILE_NAME_COLUMN = "file_name"; public static final String OTA_PACKAGE_FILE_NAME_COLUMN = "file_name";
public static final String OTA_PACKAGE_CONTENT_TYPE_COLUMN = "content_type"; public static final String OTA_PACKAGE_CONTENT_TYPE_COLUMN = "content_type";

74
dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java

@ -48,6 +48,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DATA_S
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_FILE_NAME_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_FILE_NAME_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TAG_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
@ -78,6 +79,9 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
@Column(name = OTA_PACKAGE_VERSION_COLUMN) @Column(name = OTA_PACKAGE_VERSION_COLUMN)
private String version; private String version;
@Column(name = OTA_PACKAGE_TAG_COLUMN)
private String tag;
@Column(name = OTA_PACKAGE_URL_COLUMN) @Column(name = OTA_PACKAGE_URL_COLUMN)
private String url; private String url;
@ -112,24 +116,25 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
super(); super();
} }
public OtaPackageEntity(OtaPackage firmware) { public OtaPackageEntity(OtaPackage otaPackage) {
this.createdTime = firmware.getCreatedTime(); this.createdTime = otaPackage.getCreatedTime();
this.setUuid(firmware.getUuidId()); this.setUuid(otaPackage.getUuidId());
this.tenantId = firmware.getTenantId().getId(); this.tenantId = otaPackage.getTenantId().getId();
if (firmware.getDeviceProfileId() != null) { if (otaPackage.getDeviceProfileId() != null) {
this.deviceProfileId = firmware.getDeviceProfileId().getId(); this.deviceProfileId = otaPackage.getDeviceProfileId().getId();
} }
this.type = firmware.getType(); this.type = otaPackage.getType();
this.title = firmware.getTitle(); this.title = otaPackage.getTitle();
this.version = firmware.getVersion(); this.version = otaPackage.getVersion();
this.url = firmware.getUrl(); this.tag = otaPackage.getTag();
this.fileName = firmware.getFileName(); this.url = otaPackage.getUrl();
this.contentType = firmware.getContentType(); this.fileName = otaPackage.getFileName();
this.checksumAlgorithm = firmware.getChecksumAlgorithm(); this.contentType = otaPackage.getContentType();
this.checksum = firmware.getChecksum(); this.checksumAlgorithm = otaPackage.getChecksumAlgorithm();
this.data = firmware.getData().array(); this.checksum = otaPackage.getChecksum();
this.dataSize = firmware.getDataSize(); this.data = otaPackage.getData().array();
this.additionalInfo = firmware.getAdditionalInfo(); this.dataSize = otaPackage.getDataSize();
this.additionalInfo = otaPackage.getAdditionalInfo();
} }
@Override @Override
@ -144,26 +149,27 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
@Override @Override
public OtaPackage toData() { public OtaPackage toData() {
OtaPackage firmware = new OtaPackage(new OtaPackageId(id)); OtaPackage otaPackage = new OtaPackage(new OtaPackageId(id));
firmware.setCreatedTime(createdTime); otaPackage.setCreatedTime(createdTime);
firmware.setTenantId(new TenantId(tenantId)); otaPackage.setTenantId(new TenantId(tenantId));
if (deviceProfileId != null) { if (deviceProfileId != null) {
firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); otaPackage.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
} }
firmware.setType(type); otaPackage.setType(type);
firmware.setTitle(title); otaPackage.setTitle(title);
firmware.setVersion(version); otaPackage.setVersion(version);
firmware.setUrl(url); otaPackage.setTag(tag);
firmware.setFileName(fileName); otaPackage.setUrl(url);
firmware.setContentType(contentType); otaPackage.setFileName(fileName);
firmware.setChecksumAlgorithm(checksumAlgorithm); otaPackage.setContentType(contentType);
firmware.setChecksum(checksum); otaPackage.setChecksumAlgorithm(checksumAlgorithm);
firmware.setDataSize(dataSize); otaPackage.setChecksum(checksum);
otaPackage.setDataSize(dataSize);
if (data != null) { if (data != null) {
firmware.setData(ByteBuffer.wrap(data)); otaPackage.setData(ByteBuffer.wrap(data));
firmware.setHasData(true); otaPackage.setHasData(true);
} }
firmware.setAdditionalInfo(additionalInfo); otaPackage.setAdditionalInfo(additionalInfo);
return firmware; return otaPackage;
} }
} }

73
dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java

@ -48,6 +48,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DATA_S
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_FILE_NAME_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_FILE_NAME_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TAG_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
@ -78,6 +79,9 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
@Column(name = OTA_PACKAGE_VERSION_COLUMN) @Column(name = OTA_PACKAGE_VERSION_COLUMN)
private String version; private String version;
@Column(name = OTA_PACKAGE_TAG_COLUMN)
private String tag;
@Column(name = OTA_PACKAGE_URL_COLUMN) @Column(name = OTA_PACKAGE_URL_COLUMN)
private String url; private String url;
@ -111,26 +115,27 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
super(); super();
} }
public OtaPackageInfoEntity(OtaPackageInfo firmware) { public OtaPackageInfoEntity(OtaPackageInfo otaPackageInfo) {
this.createdTime = firmware.getCreatedTime(); this.createdTime = otaPackageInfo.getCreatedTime();
this.setUuid(firmware.getUuidId()); this.setUuid(otaPackageInfo.getUuidId());
this.tenantId = firmware.getTenantId().getId(); this.tenantId = otaPackageInfo.getTenantId().getId();
this.type = firmware.getType(); this.type = otaPackageInfo.getType();
if (firmware.getDeviceProfileId() != null) { if (otaPackageInfo.getDeviceProfileId() != null) {
this.deviceProfileId = firmware.getDeviceProfileId().getId(); this.deviceProfileId = otaPackageInfo.getDeviceProfileId().getId();
} }
this.title = firmware.getTitle(); this.title = otaPackageInfo.getTitle();
this.version = firmware.getVersion(); this.version = otaPackageInfo.getVersion();
this.url = firmware.getUrl(); this.tag = otaPackageInfo.getTag();
this.fileName = firmware.getFileName(); this.url = otaPackageInfo.getUrl();
this.contentType = firmware.getContentType(); this.fileName = otaPackageInfo.getFileName();
this.checksumAlgorithm = firmware.getChecksumAlgorithm(); this.contentType = otaPackageInfo.getContentType();
this.checksum = firmware.getChecksum(); this.checksumAlgorithm = otaPackageInfo.getChecksumAlgorithm();
this.dataSize = firmware.getDataSize(); this.checksum = otaPackageInfo.getChecksum();
this.additionalInfo = firmware.getAdditionalInfo(); this.dataSize = otaPackageInfo.getDataSize();
this.additionalInfo = otaPackageInfo.getAdditionalInfo();
} }
public OtaPackageInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, OtaPackageType type, String title, String version, public OtaPackageInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, OtaPackageType type, String title, String version, String tag,
String url, String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize, String url, String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
Object additionalInfo, boolean hasData) { Object additionalInfo, boolean hasData) {
this.id = id; this.id = id;
@ -140,6 +145,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
this.type = type; this.type = type;
this.title = title; this.title = title;
this.version = version; this.version = version;
this.tag = tag;
this.url = url; this.url = url;
this.fileName = fileName; this.fileName = fileName;
this.contentType = contentType; this.contentType = contentType;
@ -162,23 +168,24 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
@Override @Override
public OtaPackageInfo toData() { public OtaPackageInfo toData() {
OtaPackageInfo firmware = new OtaPackageInfo(new OtaPackageId(id)); OtaPackageInfo otaPackageInfo = new OtaPackageInfo(new OtaPackageId(id));
firmware.setCreatedTime(createdTime); otaPackageInfo.setCreatedTime(createdTime);
firmware.setTenantId(new TenantId(tenantId)); otaPackageInfo.setTenantId(new TenantId(tenantId));
if (deviceProfileId != null) { if (deviceProfileId != null) {
firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); otaPackageInfo.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
} }
firmware.setType(type); otaPackageInfo.setType(type);
firmware.setTitle(title); otaPackageInfo.setTitle(title);
firmware.setVersion(version); otaPackageInfo.setVersion(version);
firmware.setUrl(url); otaPackageInfo.setTag(tag);
firmware.setFileName(fileName); otaPackageInfo.setUrl(url);
firmware.setContentType(contentType); otaPackageInfo.setFileName(fileName);
firmware.setChecksumAlgorithm(checksumAlgorithm); otaPackageInfo.setContentType(contentType);
firmware.setChecksum(checksum); otaPackageInfo.setChecksumAlgorithm(checksumAlgorithm);
firmware.setDataSize(dataSize); otaPackageInfo.setChecksum(checksum);
firmware.setAdditionalInfo(additionalInfo); otaPackageInfo.setDataSize(dataSize);
firmware.setHasData(hasData); otaPackageInfo.setAdditionalInfo(additionalInfo);
return firmware; otaPackageInfo.setHasData(hasData);
return otaPackageInfo;
} }
} }

5
dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java

@ -51,6 +51,7 @@ import org.thingsboard.server.dao.tenant.TenantDao;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE; import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE;
@ -318,6 +319,10 @@ public class BaseOtaPackageService implements OtaPackageService {
throw new DataValidationException("Updating otaPackage version is prohibited!"); throw new DataValidationException("Updating otaPackage version is prohibited!");
} }
if (!Objects.equals(otaPackage.getTag(), otaPackageOld.getTag())) {
throw new DataValidationException("Updating otaPackage tag is prohibited!");
}
if (!otaPackageOld.getDeviceProfileId().equals(otaPackage.getDeviceProfileId())) { if (!otaPackageOld.getDeviceProfileId().equals(otaPackage.getDeviceProfileId())) {
throw new DataValidationException("Updating otaPackage deviceProfile is prohibited!"); throw new DataValidationException("Updating otaPackage deviceProfile is prohibited!");
} }

6
dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java

@ -26,14 +26,14 @@ import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity;
import java.util.UUID; import java.util.UUID;
public interface OtaPackageInfoRepository extends CrudRepository<OtaPackageInfoEntity, UUID> { public interface OtaPackageInfoRepository extends CrudRepository<OtaPackageInfoEntity, UUID> {
@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 " + @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.tenantId = :tenantId " + "f.tenantId = :tenantId " +
"AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<OtaPackageInfoEntity> findAllByTenantId(@Param("tenantId") UUID tenantId, Page<OtaPackageInfoEntity> findAllByTenantId(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
@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 " + @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 " +
"f.tenantId = :tenantId " + "f.tenantId = :tenantId " +
"AND f.deviceProfileId = :deviceProfileId " + "AND f.deviceProfileId = :deviceProfileId " +
"AND f.type = :type " + "AND f.type = :type " +
@ -45,7 +45,7 @@ public interface OtaPackageInfoRepository extends CrudRepository<OtaPackageInfoE
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
@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") @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")
OtaPackageInfoEntity findOtaPackageInfoById(@Param("id") UUID id); OtaPackageInfoEntity findOtaPackageInfoById(@Param("id") UUID id);
@Query(value = "SELECT exists(SELECT * " + @Query(value = "SELECT exists(SELECT * " +

1
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -173,6 +173,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
type varchar(32) NOT NULL, type varchar(32) NOT NULL,
title varchar(255) NOT NULL, title varchar(255) NOT NULL,
version varchar(255) NOT NULL, version varchar(255) NOT NULL,
tag varchar(255),
url varchar(255), url varchar(255),
file_name varchar(255), file_name varchar(255),
content_type varchar(255), content_type varchar(255),

1
dao/src/main/resources/sql/schema-entities.sql

@ -188,6 +188,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
type varchar(32) NOT NULL, type varchar(32) NOT NULL,
title varchar(255) NOT NULL, title varchar(255) NOT NULL,
version varchar(255) NOT NULL, version varchar(255) NOT NULL,
tag varchar(255),
url varchar(255), url varchar(255),
file_name varchar(255), file_name varchar(255),
content_type varchar(255), content_type varchar(255),

3
ui-ngx/angular.json

@ -118,7 +118,8 @@
"jquery", "jquery",
"jquery.terminal", "jquery.terminal",
"tooltipster", "tooltipster",
"jstree" "jstree",
"qrcode"
] ]
}, },
"configurations": { "configurations": {

1
ui-ngx/package.json

@ -77,6 +77,7 @@
"objectpath": "^2.0.0", "objectpath": "^2.0.0",
"prettier": "^2.1.2", "prettier": "^2.1.2",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"qrcode": "^1.4.4",
"raphael": "^2.3.0", "raphael": "^2.3.0",
"rc-select": "~10.5.1", "rc-select": "~10.5.1",
"react": "~16.14.0", "react": "~16.14.0",

16
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html

@ -174,14 +174,9 @@
<!-- <div fxLayout="column">--> <!-- <div fxLayout="column">-->
<!-- <mat-form-field class="mat-block">--> <!-- <mat-form-field class="mat-block">-->
<!-- <mat-label>{{ 'device-profile.lwm2m.client-strategy-label' | translate }}</mat-label>--> <!-- <mat-label>{{ 'device-profile.lwm2m.client-strategy-label' | translate }}</mat-label>-->
<!-- <mat-select formControlName="clientOnlyObserveAfterConnect"--> <!-- <mat-select formControlName="clientOnlyObserveAfterConnect">-->
<!-- matTooltip="{{ 'device-profile.lwm2m.client-strategy-tip' | translate:--> <!-- <mat-option value=1>{{ 'device-profile.lwm2m.client-strategy-only-observe' | translate }}</mat-option>-->
<!-- { count: +lwm2mDeviceProfileFormGroup.get('clientOnlyObserveAfterConnect').value } }}"--> <!-- <mat-option value=2>{{ 'device-profile.lwm2m.client-strategy-read-all' | translate }}</mat-option>-->
<!-- matTooltipPosition="above">-->
<!-- <mat-option value=1>{{ 'device-profile.lwm2m.client-strategy-connect' | translate:-->
<!-- {count: 1} }}</mat-option>-->
<!-- <mat-option value=2>{{ 'device-profile.lwm2m.client-strategy-connect' | translate:-->
<!-- {count: 2} }}</mat-option>-->
<!-- </mat-select>--> <!-- </mat-select>-->
<!-- </mat-form-field>--> <!-- </mat-form-field>-->
<!-- </div>--> <!-- </div>-->
@ -194,13 +189,12 @@
</mat-tab> </mat-tab>
<mat-tab label="{{ 'device-profile.lwm2m.config-json-tab' | translate }}"> <mat-tab label="{{ 'device-profile.lwm2m.config-json-tab' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<section [formGroup]="lwm2mDeviceConfigFormGroup" style="padding: 8px 0"> <section style="padding: 8px 0">
<tb-json-object-edit <tb-json-object-edit
readonly readonly
[required]="required"
[sort]="sortFunction" [sort]="sortFunction"
label="{{ 'device-profile.transport-type-lwm2m' | translate }}" label="{{ 'device-profile.transport-type-lwm2m' | translate }}"
formControlName="configurationJson"> [ngModel]="configurationValue">
</tb-json-object-edit> </tb-json-object-edit>
</section> </section>
</ng-template> </ng-template>

37
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts

@ -14,7 +14,6 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { DeviceProfileTransportConfiguration } from '@shared/models/device.models';
import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
@ -76,7 +75,6 @@ import { takeUntil } from 'rxjs/operators';
}) })
export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validator, OnDestroy { export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validator, OnDestroy {
private configurationValue: Lwm2mProfileConfigModels;
private requiredValue: boolean; private requiredValue: boolean;
private disabled = false; private disabled = false;
private destroy$ = new Subject(); private destroy$ = new Subject();
@ -84,7 +82,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
bindingModeTypes = Object.values(BingingMode); bindingModeTypes = Object.values(BingingMode);
bindingModeTypeNamesMap = BingingModeTranslationsMap; bindingModeTypeNamesMap = BingingModeTranslationsMap;
lwm2mDeviceProfileFormGroup: FormGroup; lwm2mDeviceProfileFormGroup: FormGroup;
lwm2mDeviceConfigFormGroup: FormGroup; configurationValue: Lwm2mProfileConfigModels;
sortFunction: (key: string, value: object) => object; sortFunction: (key: string, value: object) => object;
get required(): boolean { get required(): boolean {
@ -128,9 +126,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
compositeOperationsSupport: [false] compositeOperationsSupport: [false]
}) })
}); });
this.lwm2mDeviceConfigFormGroup = this.fb.group({
configurationJson: [null, Validators.required]
});
this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').valueChanges.pipe( this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').valueChanges.pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe((fwStrategy) => { ).subscribe((fwStrategy) => {
@ -158,11 +153,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
).subscribe((value) => { ).subscribe((value) => {
this.updateDeviceProfileValue(value); this.updateDeviceProfileValue(value);
}); });
this.lwm2mDeviceConfigFormGroup.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(() => {
this.updateModel();
});
this.sortFunction = this.sortObjectKeyPathJson; this.sortFunction = this.sortObjectKeyPathJson;
} }
@ -182,10 +172,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
this.disabled = isDisabled; this.disabled = isDisabled;
if (isDisabled) { if (isDisabled) {
this.lwm2mDeviceProfileFormGroup.disable({emitEvent: false}); this.lwm2mDeviceProfileFormGroup.disable({emitEvent: false});
this.lwm2mDeviceConfigFormGroup.disable({emitEvent: false});
} else { } else {
this.lwm2mDeviceProfileFormGroup.enable({emitEvent: false}); this.lwm2mDeviceProfileFormGroup.enable({emitEvent: false});
this.lwm2mDeviceConfigFormGroup.enable({emitEvent: false});
this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.powerMode').updateValueAndValidity({onlySelf: true}); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.powerMode').updateValueAndValidity({onlySelf: true});
this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').updateValueAndValidity({onlySelf: true}); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').updateValueAndValidity({onlySelf: true});
this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').updateValueAndValidity({onlySelf: true}); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').updateValueAndValidity({onlySelf: true});
@ -196,9 +184,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
if (isDefinedAndNotNull(value) && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) { if (isDefinedAndNotNull(value) && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) {
this.configurationValue = value; this.configurationValue = value;
const defaultFormSettings = !(value.observeAttr.attribute.length && value.observeAttr.telemetry.length); const defaultFormSettings = !(value.observeAttr.attribute.length && value.observeAttr.telemetry.length);
this.lwm2mDeviceConfigFormGroup.patchValue({
configurationJson: this.configurationValue
}, {emitEvent: defaultFormSettings});
if (defaultFormSettings) { if (defaultFormSettings) {
await this.defaultProfileConfig(); await this.defaultProfileConfig();
} }
@ -227,9 +212,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
this.configurationValue.bootstrap.bootstrapServer = bootstrap; this.configurationValue.bootstrap.bootstrapServer = bootstrap;
this.configurationValue.bootstrap.lwm2mServer = lwm2m; this.configurationValue.bootstrap.lwm2mServer = lwm2m;
this.lwm2mDeviceConfigFormGroup.patchValue({
configurationJson: this.configurationValue
}, {emitEvent: false});
this.lwm2mDeviceProfileFormGroup.patchValue({ this.lwm2mDeviceProfileFormGroup.patchValue({
bootstrap: this.configurationValue.bootstrap bootstrap: this.configurationValue.bootstrap
}, {emitEvent: false}); }, {emitEvent: false});
@ -265,6 +247,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
swUpdateResource: this.configurationValue.clientLwM2mSettings.swUpdateResource || '', swUpdateResource: this.configurationValue.clientLwM2mSettings.swUpdateResource || '',
powerMode: this.configurationValue.clientLwM2mSettings.powerMode || PowerMode.DRX, powerMode: this.configurationValue.clientLwM2mSettings.powerMode || PowerMode.DRX,
edrxCycle: this.configurationValue.clientLwM2mSettings.edrxCycle || 0, edrxCycle: this.configurationValue.clientLwM2mSettings.edrxCycle || 0,
pagingTransmissionWindow: this.configurationValue.clientLwM2mSettings.pagingTransmissionWindow || 0,
psmActivityTimer: this.configurationValue.clientLwM2mSettings.psmActivityTimer || 0,
compositeOperationsSupport: this.configurationValue.clientLwM2mSettings.compositeOperationsSupport || false compositeOperationsSupport: this.configurationValue.clientLwM2mSettings.compositeOperationsSupport || false
} }
}, },
@ -277,9 +261,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
} }
private updateModel = (): void => { private updateModel = (): void => {
let configuration: DeviceProfileTransportConfiguration = null; let configuration: Lwm2mProfileConfigModels = null;
if (this.lwm2mDeviceConfigFormGroup.valid && this.lwm2mDeviceProfileFormGroup.valid) { if (this.lwm2mDeviceProfileFormGroup.valid) {
configuration = this.lwm2mDeviceConfigFormGroup.value.configurationJson; configuration = this.configurationValue;
} }
this.propagateChange(configuration); this.propagateChange(configuration);
} }
@ -299,7 +283,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
this.configurationValue.bootstrap.lwm2mServer = config.bootstrap.lwm2mServer; this.configurationValue.bootstrap.lwm2mServer = config.bootstrap.lwm2mServer;
this.configurationValue.bootstrap.servers = config.bootstrap.servers; this.configurationValue.bootstrap.servers = config.bootstrap.servers;
this.configurationValue.clientLwM2mSettings = config.clientLwM2mSettings; this.configurationValue.clientLwM2mSettings = config.clientLwM2mSettings;
this.upDateJsonAllConfig();
this.updateModel(); this.updateModel();
} }
@ -327,7 +310,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
} }
if (isDefinedAndNotNull(keyNameJson)) { if (isDefinedAndNotNull(keyNameJson)) {
this.configurationValue.observeAttr.keyName = this.validateKeyNameObjects(keyNameJson, attributeArray, telemetryArray); this.configurationValue.observeAttr.keyName = this.validateKeyNameObjects(keyNameJson, attributeArray, telemetryArray);
this.upDateJsonAllConfig();
this.updateKeyNameObjects(objectLwM2MS); this.updateKeyNameObjects(objectLwM2MS);
} }
} }
@ -513,12 +495,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
return (objectsIds.size > 0) ? Array.from(objectsIds) : []; return (objectsIds.size > 0) ? Array.from(objectsIds) : [];
} }
private upDateJsonAllConfig = (): void => {
this.lwm2mDeviceConfigFormGroup.patchValue({
configurationJson: this.configurationValue
}, {emitEvent: false});
}
addObjectsList = (value: ObjectLwM2M[]): void => { addObjectsList = (value: ObjectLwM2M[]): void => {
this.updateObserveAttrTelemetryObjectFormGroup(value); this.updateObserveAttrTelemetryObjectFormGroup(value);
} }
@ -536,7 +512,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
this.removeKeyNameFromJson(value.keyId); this.removeKeyNameFromJson(value.keyId);
this.removeAttributesFromJson(value.keyId); this.removeAttributesFromJson(value.keyId);
this.updateObserveAttrTelemetryObjectFormGroup(objectsOld); this.updateObserveAttrTelemetryObjectFormGroup(objectsOld);
this.upDateJsonAllConfig();
} }
private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, keyId: string): void => { private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, keyId: string): void => {

21
ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.html

@ -0,0 +1,21 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div fxLayout="column" fxLayoutAlign="center center" style="width: 100%; height: 100%;">
<canvas fxFlex #canvas [ngStyle]="{display: qrCodeText ? 'block' : 'none'}"></canvas>
<div *ngIf="!qrCodeText" translate>entity.no-data</div>
</div>

129
ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts

@ -0,0 +1,129 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import QRCode from 'qrcode';
import {
fillPattern,
parseData,
parseFunction,
processPattern,
safeExecute
} from '@home/components/widget/lib/maps/common-maps-utils';
import { FormattedData } from '@home/components/widget/lib/maps/map-models';
import { DatasourceData } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
interface QrCodeWidgetSettings {
qrCodeTextPattern: string;
useQrCodeTextFunction: boolean;
qrCodeTextFunction: string;
}
type QrCodeTextFunction = (data: FormattedData) => string;
@Component({
selector: 'tb-qrcode-widget',
templateUrl: './qrcode-widget.component.html',
styleUrls: []
})
export class QrCodeWidgetComponent extends PageComponent implements OnInit, AfterViewInit {
settings: QrCodeWidgetSettings;
qrCodeTextFunction: QrCodeTextFunction;
@Input()
ctx: WidgetContext;
qrCodeText: string;
private viewInited: boolean;
private scheduleUpdateCanvas: boolean;
@ViewChild('canvas', {static: false}) canvasRef: ElementRef<HTMLCanvasElement>;
constructor(protected store: Store<AppState>,
protected cd: ChangeDetectorRef) {
super(store);
}
ngOnInit(): void {
this.ctx.$scope.qrCodeWidget = this;
this.settings = this.ctx.settings;
this.qrCodeTextFunction = this.settings.useQrCodeTextFunction ? parseFunction(this.settings.qrCodeTextFunction, ['data']) : null;
}
ngAfterViewInit(): void {
this.viewInited = true;
if (this.scheduleUpdateCanvas) {
this.scheduleUpdateCanvas = false;
this.updateCanvas();
}
}
public onDataUpdated() {
let initialData: DatasourceData[];
let qrCodeText: string;
if (this.ctx.data?.length) {
initialData = this.ctx.data;
} else if (this.ctx.datasources?.length) {
initialData = [
{
datasource: this.ctx.datasources[0],
dataKey: {
type: DataKeyType.attribute,
name: 'empty'
},
data: []
}
];
}
if (initialData) {
const data = parseData(initialData);
const dataSourceData = data[0];
const pattern = this.settings.useQrCodeTextFunction ?
safeExecute(this.qrCodeTextFunction, [dataSourceData]) : this.settings.qrCodeTextPattern;
const replaceInfo = processPattern(pattern, data);
qrCodeText = fillPattern(pattern, replaceInfo, dataSourceData);
}
this.updateQrCodeText(qrCodeText);
}
private updateQrCodeText(newQrCodeText: string): void {
if (this.qrCodeText !== newQrCodeText) {
this.qrCodeText = newQrCodeText;
if (this.qrCodeText) {
this.updateCanvas();
}
this.cd.detectChanges();
}
}
private updateCanvas() {
if (this.viewInited) {
QRCode.toCanvas(this.canvasRef.nativeElement, this.qrCodeText);
this.canvasRef.nativeElement.style.width = 'auto';
this.canvasRef.nativeElement.style.height = 'auto';
} else {
this.scheduleUpdateCanvas = true;
}
}
}

7
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -39,6 +39,7 @@ import { NavigationCardsWidgetComponent } from '@home/components/widget/lib/navi
import { NavigationCardWidgetComponent } from '@home/components/widget/lib/navigation-card-widget.component'; import { NavigationCardWidgetComponent } from '@home/components/widget/lib/navigation-card-widget.component';
import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-overview-widget.component'; import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-overview-widget.component';
import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input-widget.component'; import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input-widget.component';
import { QrCodeWidgetComponent } from '@home/components/widget/lib/qrcode-widget.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -58,7 +59,8 @@ import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input
PhotoCameraInputWidgetComponent, PhotoCameraInputWidgetComponent,
GatewayFormComponent, GatewayFormComponent,
NavigationCardsWidgetComponent, NavigationCardsWidgetComponent,
NavigationCardWidgetComponent NavigationCardWidgetComponent,
QrCodeWidgetComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -80,7 +82,8 @@ import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input
PhotoCameraInputWidgetComponent, PhotoCameraInputWidgetComponent,
GatewayFormComponent, GatewayFormComponent,
NavigationCardsWidgetComponent, NavigationCardsWidgetComponent,
NavigationCardWidgetComponent NavigationCardWidgetComponent,
QrCodeWidgetComponent
], ],
providers: [ providers: [
CustomDialogService, CustomDialogService,

7
ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts

@ -59,9 +59,10 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot
this.config.columns.push( this.config.columns.push(
new DateEntityTableColumn<OtaPackageInfo>('createdTime', 'common.created-time', this.datePipe, '150px'), new DateEntityTableColumn<OtaPackageInfo>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<OtaPackageInfo>('title', 'ota-update.title', '20%'), new EntityTableColumn<OtaPackageInfo>('title', 'ota-update.title', '15%'),
new EntityTableColumn<OtaPackageInfo>('version', 'ota-update.version', '20%'), new EntityTableColumn<OtaPackageInfo>('version', 'ota-update.version', '15%'),
new EntityTableColumn<OtaPackageInfo>('type', 'ota-update.package-type', '20%', entity => { new EntityTableColumn<OtaPackageInfo>('tag', 'ota-update.version-tag', '15%'),
new EntityTableColumn<OtaPackageInfo>('type', 'ota-update.package-type', '15%', entity => {
return this.translate.instant(OtaUpdateTypeTranslationMap.get(entity.type)); return this.translate.instant(OtaUpdateTypeTranslationMap.get(entity.type));
}), }),
new EntityTableColumn<OtaPackageInfo>('url', 'ota-update.direct-url', '20%', entity => { new EntityTableColumn<OtaPackageInfo>('url', 'ota-update.direct-url', '20%', entity => {

9
ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html

@ -74,6 +74,11 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field class="mat-block" fxFlex style="margin-bottom: 8px">
<mat-label translate>ota-update.version-tag</mat-label>
<input matInput formControlName="tag" type="text" [readonly]="!isAdd">
<mat-hint *ngIf="isAdd" translate>ota-update.version-tag-hint</mat-hint>
</mat-form-field>
<tb-device-profile-autocomplete <tb-device-profile-autocomplete
formControlName="deviceProfileId" formControlName="deviceProfileId"
required required
@ -94,8 +99,8 @@
<section *ngIf="isAdd"> <section *ngIf="isAdd">
<div class="mat-caption" style="margin: -8px 0 8px;" translate>ota-update.warning-after-save-no-edit</div> <div class="mat-caption" style="margin: -8px 0 8px;" translate>ota-update.warning-after-save-no-edit</div>
<mat-radio-group formControlName="isURL" fxLayoutGap="16px"> <mat-radio-group formControlName="isURL" fxLayoutGap="16px">
<mat-radio-button [value]="false">Upload binary file</mat-radio-button> <mat-radio-button [value]="false">{{ "ota-update.upload-binary-file" | translate }}</mat-radio-button>
<mat-radio-button [value]="true">Use external URL</mat-radio-button> <mat-radio-button [value]="true">{{ "ota-update.use-external-url" | translate }}</mat-radio-button>
</mat-radio-group> </mat-radio-group>
</section> </section>
<section *ngIf="!entityForm.get('isURL').value"> <section *ngIf="!entityForm.get('isURL').value">

49
ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts

@ -15,7 +15,7 @@
/// ///
import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { Subject } from 'rxjs'; import { combineLatest, Subject } from 'rxjs';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -30,7 +30,7 @@ import {
OtaUpdateTypeTranslationMap OtaUpdateTypeTranslationMap
} from '@shared/models/ota-package.models'; } from '@shared/models/ota-package.models';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { filter, takeUntil } from 'rxjs/operators'; import { filter, startWith, takeUntil } from 'rxjs/operators';
import { isNotEmptyStr } from '@core/utils'; import { isNotEmptyStr } from '@core/utils';
@Component({ @Component({
@ -56,22 +56,33 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
ngOnInit() { ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.entityForm.get('isURL').valueChanges.pipe( if (this.isAdd) {
filter(() => this.isAdd), this.entityForm.get('isURL').valueChanges.pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe((isURL) => { ).subscribe((isURL) => {
if (isURL === false) { if (isURL === false) {
this.entityForm.get('url').clearValidators(); this.entityForm.get('url').clearValidators();
this.entityForm.get('file').setValidators(Validators.required); this.entityForm.get('file').setValidators(Validators.required);
this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); this.entityForm.get('url').updateValueAndValidity({emitEvent: false});
this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); this.entityForm.get('file').updateValueAndValidity({emitEvent: false});
} else { } else {
this.entityForm.get('file').clearValidators(); this.entityForm.get('file').clearValidators();
this.entityForm.get('url').setValidators([Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]); this.entityForm.get('url').setValidators([Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]);
this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); this.entityForm.get('file').updateValueAndValidity({emitEvent: false});
this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); this.entityForm.get('url').updateValueAndValidity({emitEvent: false});
} }
}); });
combineLatest([
this.entityForm.get('title').valueChanges.pipe(startWith('')),
this.entityForm.get('version').valueChanges.pipe(startWith(''))
]).pipe(
filter(() => this.entityForm.get('tag').pristine),
takeUntil(this.destroy$)
).subscribe(([title, version]) => {
const tag = (`${title} ${version}`).trim();
this.entityForm.get('tag').patchValue(tag);
});
}
} }
ngOnDestroy() { ngOnDestroy() {
@ -92,6 +103,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
const form = this.fb.group({ const form = this.fb.group({
title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]], version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]],
tag: [entity ? entity.tag : '', [Validators.maxLength(255)]],
type: [entity?.type ? entity.type : OtaUpdateType.FIRMWARE, Validators.required], type: [entity?.type ? entity.type : OtaUpdateType.FIRMWARE, Validators.required],
deviceProfileId: [entity ? entity.deviceProfileId : null, Validators.required], deviceProfileId: [entity ? entity.deviceProfileId : null, Validators.required],
checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256], checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256],
@ -119,6 +131,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
this.entityForm.patchValue({ this.entityForm.patchValue({
title: entity.title, title: entity.title,
version: entity.version, version: entity.version,
tag: entity.tag,
type: entity.type, type: entity.type,
deviceProfileId: entity.deviceProfileId, deviceProfileId: entity.deviceProfileId,
checksumAlgorithm: entity.checksumAlgorithm, checksumAlgorithm: entity.checksumAlgorithm,

1
ui-ngx/src/app/shared/models/ota-package.models.ts

@ -91,6 +91,7 @@ export interface OtaPackageInfo extends BaseData<OtaPackageId> {
deviceProfileId?: DeviceProfileId; deviceProfileId?: DeviceProfileId;
title?: string; title?: string;
version?: string; version?: string;
tag?: string;
hasData?: boolean; hasData?: boolean;
url?: string; url?: string;
fileName: string; fileName: string;

13
ui-ngx/src/assets/locale/locale.constant-cs_CZ.json

@ -1236,14 +1236,12 @@
"object-list": "SEznam objektů", "object-list": "SEznam objektů",
"object-list-empty": "Žádné objekty nebyly vybrány.", "object-list-empty": "Žádné objekty nebyly vybrány.",
"no-objects-matching": "Žádné objekty odpovídající '{{object}}' nebyly nalezeny.", "no-objects-matching": "Žádné objekty odpovídající '{{object}}' nebyly nalezeny.",
"valid-id-instance-no-min": "Instance číslo '{{instance}}' nebyla validována. Mininimální hodnota='{{min}}'",
"valid-id-instance-no-max": "Instance číslo '{{instance}}' nebyla validována. Maximální hodnota='{{max}}'",
"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} }",
"model-tab": "LWM2M model", "model-tab": "LWM2M model",
"add-new-instances": "Přidat nové instance", "add-new-instances": "Přidat nové instance",
"instances-list": "Seznam instancí", "instances-list": "Seznam instancí",
"instances-input": "Vstupní hodnota Id instance", "instances-list-required": "Seznam instancí je povinný",
"instances-input-holder": "Vstupní číslo instance...", "instance-id-pattern": "Instance číslo musí být kladné číslo.",
"instance-id-max": "Maximální instance číslo hodnota {{max}}.",
"instance": "Instance", "instance": "Instance",
"resource-label": "Název zdroje #ID", "resource-label": "Název zdroje #ID",
"observe-label": "Pozorování", "observe-label": "Pozorování",
@ -1266,7 +1264,6 @@
"view-attribute": "Zobrazit atribut", "view-attribute": "Zobrazit atribut",
"remove-attribute": "Odebrat atribut", "remove-attribute": "Odebrat atribut",
"mode": "Režim konfigurace bezpečnosti", "mode": "Režim konfigurace bezpečnosti",
"pattern_hex_dec": "{ count, plural, 0 {musí být v hexadecimálním formátu} other {musí být # znaků} }",
"servers": "Servery", "servers": "Servery",
"short-id": "Krátké ID", "short-id": "Krátké ID",
"short-id-required": "Krátké ID je povinné.", "short-id-required": "Krátké ID je povinné.",
@ -1316,8 +1313,8 @@
"others-tab": "Ostatní nastavení", "others-tab": "Ostatní nastavení",
"client-strategy": "Strategie klienta při připojování", "client-strategy": "Strategie klienta při připojování",
"client-strategy-label": "Strategie", "client-strategy-label": "Strategie",
"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} }", "client-strategy-only-observe": "Klientovi je odeslán pouze observe požadavek po úvodním spojení",
"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í.} }", "client-strategy-read-all": "Načti všechny zdroje a observer požadavky na klienta po registraci",
"fw-update": "Aktualizace firmware", "fw-update": "Aktualizace firmware",
"fw-update-strategy": "Strategie aktualizace firmware", "fw-update-strategy": "Strategie aktualizace firmware",
"fw-update-strategy-data": "Odeslat (push) aktualizaci firmware jako binární soubor pomocí Object 19 a Resource 0 (Data)", "fw-update-strategy-data": "Odeslat (push) aktualizaci firmware jako binární soubor pomocí Object 19 a Resource 0 (Data)",

9
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1273,7 +1273,6 @@
"view-attribute": "View attribute", "view-attribute": "View attribute",
"remove-attribute": "Remove attribute", "remove-attribute": "Remove attribute",
"mode": "Security config mode", "mode": "Security config mode",
"pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }",
"servers": "Servers", "servers": "Servers",
"short-id": "Short ID", "short-id": "Short ID",
"short-id-required": "Short ID is required.", "short-id-required": "Short ID is required.",
@ -1323,8 +1322,8 @@
"others-tab": "Other settings", "others-tab": "Other settings",
"client-strategy": "Client strategy when connecting", "client-strategy": "Client strategy when connecting",
"client-strategy-label": "Strategy", "client-strategy-label": "Strategy",
"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} }", "client-strategy-only-observe": "Only Observe Request to the client after the initial connection",
"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.} }", "client-strategy-read-all": "Read All Resources & Observe Request to the client after registration",
"fw-update": "Firmware update", "fw-update": "Firmware update",
"fw-update-strategy": "Firmware update strategy", "fw-update-strategy": "Firmware update strategy",
"fw-update-strategy-data": "Push firmware update as binary file using Object 19 and Resource 0 (Data)", "fw-update-strategy-data": "Push firmware update as binary file using Object 19 and Resource 0 (Data)",
@ -2342,8 +2341,12 @@
"firmware": "Firmware", "firmware": "Firmware",
"software": "Software" "software": "Software"
}, },
"upload-binary-file": "Upload binary file",
"use-external-url": "Use external URL",
"version": "Version", "version": "Version",
"version-required": "Version is required.", "version-required": "Version is required.",
"version-tag": "Version Tag",
"version-tag-hint": "Custom tag should match the package version reported by your device.",
"warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type."
}, },
"position": { "position": {

52
ui-ngx/yarn.lock

@ -2774,7 +2774,25 @@ browserstack@^1.5.1:
dependencies: dependencies:
https-proxy-agent "^2.2.1" https-proxy-agent "^2.2.1"
buffer-from@^1.0.0: buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
buffer-from@^1.0.0, buffer-from@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
@ -2798,7 +2816,7 @@ buffer@^4.3.0:
ieee754 "^1.1.4" ieee754 "^1.1.4"
isarray "^1.0.0" isarray "^1.0.0"
buffer@^5.5.0: buffer@^5.4.3, buffer@^5.5.0:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
@ -3953,6 +3971,11 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0" miller-rabin "^4.0.0"
randombytes "^2.0.0" randombytes "^2.0.0"
dijkstrajs@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257"
integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==
dir-glob@^3.0.1: dir-glob@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@ -5670,6 +5693,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isarray@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isbinaryfile@^4.0.6: isbinaryfile@^4.0.6:
version "4.0.6" version "4.0.6"
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
@ -7503,6 +7531,11 @@ pkg-dir@^4.1.0:
dependencies: dependencies:
find-up "^4.0.0" find-up "^4.0.0"
pngjs@^3.3.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
pnp-webpack-plugin@1.6.4: pnp-webpack-plugin@1.6.4:
version "1.6.4" version "1.6.4"
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
@ -8007,6 +8040,19 @@ qjobs@^1.2.0:
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
qrcode@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83"
integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==
dependencies:
buffer "^5.4.3"
buffer-alloc "^1.2.0"
buffer-from "^1.1.1"
dijkstrajs "^1.0.1"
isarray "^2.0.1"
pngjs "^3.3.0"
yargs "^13.2.4"
qs@6.7.0: qs@6.7.0:
version "6.7.0" version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@ -10389,7 +10435,7 @@ yargs-parser@^20.2.2:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
yargs@^13.3.2: yargs@^13.2.4, yargs@^13.3.2:
version "13.3.2" version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==

Loading…
Cancel
Save