diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 79f732bca8..3cd26a5624 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -149,6 +149,24 @@ "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: \\\"FROM\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" \\\"+ data['temperature'] +\\\" °C\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Entities hierarchy\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"widgetStyle\":{},\"actions\":{}}" } + }, + { + "alias": "qr_code", + "name": "QR Code", + "image": "data:image/png;base64,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", + "description": "Displays QR code of calculated text from configured pattern or function with applied attributes or timeseries values.", + "descriptor": { + "type": "latest", + "sizeX": 4, + "sizeY": 3.5, + "resources": [], + "templateHtml": "\n", + "templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}", + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.qrCodeWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true,\n singleEntity: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"QR Code\",\n \"properties\": {\n \"qrCodeTextPattern\": {\n \"title\": \"QR code text pattern (for ex. '${entityName} | ${keyName} - some text.')\",\n \"type\": \"string\",\n \"default\": \"${entityName}\"\n },\n \"useQrCodeTextFunction\": {\n \"title\": \"Use QR code text function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"qrCodeTextFunction\": {\n \"title\": \"QR code text function: f(data)\",\n \"type\": \"string\",\n \"default\": \"return data['entityName'];\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"qrCodeTextPattern\",\n \"useQrCodeTextFunction\",\n {\n \"key\": \"qrCodeTextFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7036904308224163,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"qrCodeTextPattern\":\"${entityName}\",\"useQrCodeTextFunction\":false,\"qrCodeTextFunction\":\"return data['entityName'];\"},\"title\":\"QR Code\"}" + } } ] } diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index 479e2979e1..50f538d5dc 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/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, title varchar(255) NOT NULL, version varchar(255) NOT NULL, + tag varchar(255), url varchar(255), file_name varchar(255), content_type varchar(255), diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 5ea82c8c55..15186e26e7 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/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.rpc.ToDeviceRpcRequest; 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.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; @@ -200,7 +201,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { 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); } @@ -316,7 +317,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { .setOneway(request.isOneway()) .setPersisted(request.isPersisted()) .build(); - sendToTransport(rpcRequest, sessionId, nodeId); }; } @@ -353,9 +353,26 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { if (msg.hasPersistedRpcResponseMsg()) { processPersistedRpcResponses(context, sessionInfo, msg.getPersistedRpcResponseMsg()); } + if (msg.hasUplinkNotificationMsg()) { + processUplinkNotificationMsg(context, sessionInfo, msg.getUplinkNotificationMsg()); + } 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) { DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs()); @@ -597,7 +614,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { void processCredentialsUpdate(TbActorMsg msg) { if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { - log.info("1) LwM2Mtype: "); sessions.forEach((k, v) -> { notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials()); }); @@ -614,7 +630,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!"); } - private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) { SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto .newBuilder() @@ -628,7 +643,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) { - log.info("2) LwM2Mtype: "); ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder(); notification.addCredentialsId(deviceCredentials.getCredentialsId()); notification.addCredentialsValue(deviceCredentials.getCredentialsValue()); diff --git a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java index 153b5b5c93..4c0a55da70 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java @@ -146,6 +146,7 @@ public class OtaPackageController extends BaseController { otaPackage.setType(info.getType()); otaPackage.setTitle(info.getTitle()); otaPackage.setVersion(info.getVersion()); + otaPackage.setTag(info.getTag()); otaPackage.setAdditionalInfo(info.getAdditionalInfo()); ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); diff --git a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java index 2d6de7cfdf..4e5a104577 100644 --- a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java +++ b/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.SIZE; 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.TS; import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL; @@ -246,6 +247,11 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { List 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(), 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 StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name()))); @@ -289,6 +295,9 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { List attributes = new ArrayList<>(); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle()))); 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()) { attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl()))); List attrToRemove = new ArrayList<>(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 3b25bd1290..329e74d702 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/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_DURATION = "alarmConditionDuration"; 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() { return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE}; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java index f27c90c20b..d50aa70dcb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java @@ -37,6 +37,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo tbCoreNotificationTopics = new HashMap<>(); private Map tbRuleEngineNotificationTopics = new HashMap<>(); + private Map> tbTransportServicesByType = new HashMap<>(); private List currentOtherServices; private HashFunction hashFunction; @@ -127,6 +128,7 @@ public class HashPartitionService implements PartitionService { @Override public synchronized void recalculatePartitions(ServiceInfo currentService, List otherServices) { + tbTransportServicesByType.clear(); logServiceInfo(currentService); otherServices.forEach(this::logServiceInfo); Map> queueServicesMap = new HashMap<>(); @@ -229,6 +231,12 @@ public class HashPartitionService implements PartitionService { return Math.abs(hash % partitions); } + @Override + public int countTransportsByType(String type) { + var list = tbTransportServicesByType.get(type); + return list == null ? 0 : list.size(); + } + private Map> getServiceKeyListMap(List services) { final Map> currentMap = new HashMap<>(); services.forEach(serviceInfo -> { @@ -332,6 +340,9 @@ public class HashPartitionService implements PartitionService { 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 servers, Integer partitionIdx) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java index 20c59378e8..be6752cfed 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java +++ b/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); int resolvePartitionIndex(UUID entityId, int partitions); + + int countTransportsByType(String type); } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 811704fec0..aebb212dae 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -341,6 +341,10 @@ message ToDeviceRpcResponseMsg { string payload = 2; } +message UplinkNotificationMsg { + int64 uplinkTs = 1; +} + message ToDevicePersistedRpcResponseMsg { int32 requestId = 1; int64 requestIdMSB = 2; @@ -453,6 +457,7 @@ message TransportToDeviceActorMsg { ProvisionDeviceRequestMsg provisionDevice = 9; ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 10; SendPendingRPCMsg sendPendingRPC = 11; + UplinkNotificationMsg uplinkNotificationMsg = 12; } message TransportToRuleEngineMsg { @@ -713,6 +718,7 @@ message ToTransportMsg { ToTransportUpdateCredentialsProto toTransportUpdateCredentialsNotification = 11; ResourceUpdateMsg resourceUpdateMsg = 12; ResourceDeleteMsg resourceDeleteMsg = 13; + UplinkNotificationMsg uplinkNotificationMsg = 14; } message UsageStatsKVProto{ diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index 93096c901e..66fd870dc9 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/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.thingsboard.server.coapserver.CoapServerService; 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.ota.OtaPackageType; import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; @@ -72,6 +73,6 @@ public class CoapTransportService implements TbTransportService { @Override public String getName() { - return "COAP"; + return DataConstants.COAP_TRANSPORT_NAME; } } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 3bc53fefe5..757ec45961 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/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.extern.slf4j.Slf4j; 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.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; 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.DeviceProfile; 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.ValidateDeviceCredentialsResponse; 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.TbCoapMessageObserver; import org.thingsboard.server.transport.coap.TransportConfigurationContainer; @@ -81,6 +81,7 @@ public class DefaultCoapClientContext implements CoapClientContext { private final CoapTransportContext transportContext; private final TransportService transportService; private final TransportDeviceProfileCache profileCache; + private final PartitionService partitionService; private final ConcurrentMap clients = new ConcurrentHashMap<>(); private final ConcurrentMap 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(); PowerSavingConfiguration profileSettings = null; if (powerMode == null) { @@ -174,12 +175,12 @@ public class DefaultCoapClientContext implements CoapClientContext { } } if (powerMode == null || PowerMode.DRX.equals(powerMode)) { - client.updateLastUplinkTime(); + client.updateLastUplinkTime(uplinkTs); return; } client.lock(); try { - long uplinkTime = client.updateLastUplinkTime(); + long uplinkTime = client.updateLastUplinkTime(uplinkTs); long timeout; if (PowerMode.PSM.equals(powerMode)) { Long psmActivityTimer = client.getPsmActivityTimer(); @@ -214,6 +215,9 @@ public class DefaultCoapClientContext implements CoapClientContext { return null; }, timeout, TimeUnit.MILLISECONDS); 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 { client.unlock(); } @@ -544,6 +548,11 @@ public class DefaultCoapClientContext implements CoapClientContext { 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) { if (attrs.getObserveRelation() != null) { attrs.getObserveRelation().cancel(); @@ -562,7 +571,11 @@ public class DefaultCoapClientContext implements CoapClientContext { @Override 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); if (changed) { log.debug("[{}] client is awake", client.getDeviceId()); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/TbCoapClientState.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/TbCoapClientState.java index f106f57961..88393dbbbc 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/TbCoapClientState.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/TbCoapClientState.java @@ -97,9 +97,11 @@ public class TbCoapClientState { lock.unlock(); } - public long updateLastUplinkTime() { - this.lastUplinkTime = System.currentTimeMillis(); - this.firstEdrxDownlink = true; + public long updateLastUplinkTime(long ts) { + if (ts > lastUplinkTime) { + this.lastUplinkTime = ts; + this.firstEdrxDownlink = true; + } return lastUplinkTime; } diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index 874dd6bdd5..9c258ef00f 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/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.RestController; 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.TbTransportService; import org.thingsboard.server.common.data.id.DeviceId; @@ -436,7 +437,7 @@ public class DeviceApiController implements TbTransportService { @Override public String getName() { - return "HTTP"; + return DataConstants.HTTP_TRANSPORT_NAME; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 2d3d0c97da..356548b2bd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/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.springframework.stereotype.Component; 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.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; @@ -177,7 +178,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { @Override public String getName() { - return "LWM2M"; + return DataConstants.LWM2M_TRANSPORT_NAME; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java index 0d2ea53f8a..8cba658859 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java +++ b/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) { String newFirmwareTitle = null; String newFirmwareVersion = null; + String newFirmwareTag = null; String newFirmwareUrl = null; String newSoftwareTitle = null; String newSoftwareVersion = null; + String newSoftwareTag = null; String newSoftwareUrl = null; List otherAttributes = new ArrayList<>(); for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) { @@ -131,12 +133,16 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { newFirmwareTitle = getStrValue(tsKvProto); } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION.equals(attrName)) { newFirmwareVersion = getStrValue(tsKvProto); + } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_TAG.equals(attrName)) { + newFirmwareTag = getStrValue(tsKvProto); } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_URL.equals(attrName)) { newFirmwareUrl = getStrValue(tsKvProto); } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE.equals(attrName)) { newSoftwareTitle = getStrValue(tsKvProto); } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) { newSoftwareVersion = getStrValue(tsKvProto); + } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TAG.equals(attrName)) { + newSoftwareTag = getStrValue(tsKvProto); } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) { newSoftwareUrl = getStrValue(tsKvProto); }else { @@ -144,10 +150,10 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { } } 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) { - otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl)); + otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl), Optional.ofNullable(newSoftwareTag)); } if (!otherAttributes.isEmpty()) { onAttributesUpdate(lwM2MClient, otherAttributes); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 070cff7666..a3aba7ca0f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -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_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 SOFTWARE_VERSION = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION); 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 FIRMWARE_UPDATE_COAP_RESOURCE = "tbfw"; @@ -165,6 +167,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl if (fwInfo.isSupported()) { attributesToFetch.add(FIRMWARE_TITLE); attributesToFetch.add(FIRMWARE_VERSION); + attributesToFetch.add(FIRMWARE_TAG); attributesToFetch.add(FIRMWARE_URL); } @@ -172,6 +175,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl if (swInfo.isSupported()) { attributesToFetch.add(SOFTWARE_TITLE); attributesToFetch.add(SOFTWARE_VERSION); + attributesToFetch.add(SOFTWARE_TAG); attributesToFetch.add(SOFTWARE_URL); } @@ -186,17 +190,19 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl if (fwInfo.isSupported()) { Optional newFwTitle = getAttributeValue(attrs, FIRMWARE_TITLE); Optional newFwVersion = getAttributeValue(attrs, FIRMWARE_VERSION); + Optional newFwTag = getAttributeValue(attrs, FIRMWARE_TAG); Optional newFwUrl = getAttributeValue(attrs, FIRMWARE_URL); if (newFwTitle.isPresent() && newFwVersion.isPresent()) { - onTargetFirmwareUpdate(client, newFwTitle.get(), newFwVersion.get(), newFwUrl); + onTargetFirmwareUpdate(client, newFwTitle.get(), newFwVersion.get(), newFwUrl, newFwTag); } } if (swInfo.isSupported()) { Optional newSwTitle = getAttributeValue(attrs, SOFTWARE_TITLE); Optional newSwVersion = getAttributeValue(attrs, SOFTWARE_VERSION); + Optional newSwTag = getAttributeValue(attrs, SOFTWARE_TAG); Optional newSwUrl = getAttributeValue(attrs, SOFTWARE_URL); if (newSwTitle.isPresent() && newSwVersion.isPresent()) { - onTargetSoftwareUpdate(client, newSwTitle.get(), newSwVersion.get(), newSwUrl); + onTargetSoftwareUpdate(client, newSwTitle.get(), newSwVersion.get(), newSwUrl, newSwTag); } } }, throwable -> { @@ -216,9 +222,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } @Override - public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional newFirmwareUrl) { + public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional newFirmwareUrl, Optional newFirmwareTag) { LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); - fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl); + fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl, newFirmwareTag); update(fwInfo); startFirmwareUpdateIfNeeded(client, fwInfo); } @@ -354,9 +360,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } @Override - public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion, Optional newFirmwareUrl) { + public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion, Optional newSoftwareUrl, Optional newSoftwareTag) { LwM2MClientSwOtaInfo fwInfo = getOrInitSwInfo(client); - fwInfo.updateTarget(newSoftwareTitle, newSoftwareVersion, newFirmwareUrl); + fwInfo.updateTarget(newSoftwareTitle, newSoftwareVersion, newSoftwareUrl, newSoftwareTag); update(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!"); } else if (fwInfo.isUpdateRequired()) { 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()); } else { log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index 48936862f3..5ce581c223 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -32,6 +32,7 @@ public abstract class LwM2MClientOtaInfo { protected String targetName; protected String targetVersion; + protected String targetTag; protected String targetUrl; //TODO: use value from device if applicable; @@ -52,10 +53,11 @@ public abstract class LwM2MClientOtaInfo { this.strategy = strategy; } - public void updateTarget(String targetName, String targetVersion, Optional newTargetUrl) { + public void updateTarget(String targetName, String targetVersion, Optional newTargetUrl, Optional newTargetTag) { this.targetName = targetName; this.targetVersion = targetVersion; this.targetUrl = newTargetUrl.orElse(null); + this.targetTag = newTargetTag.orElse(null); } @JsonIgnore @@ -64,13 +66,18 @@ public abstract class LwM2MClientOtaInfo { return false; } else { String targetPackageId = getPackageId(targetName, targetVersion); - String currentPackageIdUsingObject5 = getPackageId(currentName, currentVersion); + String currentPackageId = getPackageId(currentName, currentVersion); if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) { return false; } else { - if (targetPackageId.equals(currentPackageIdUsingObject5)) { + if (targetPackageId.equals(currentPackageId)) { + return false; + } else if (StringUtils.isNotEmpty(targetTag) && targetTag.equals(currentPackageId)) { return false; } else if (StringUtils.isNotEmpty(currentVersion3)) { + if (StringUtils.isNotEmpty(targetTag) && currentVersion3.contains(targetTag)) { + return false; + } return !currentVersion3.contains(targetPackageId); } else { return true; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java index df9505f887..b03fe81d28 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java +++ b/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 onTargetFirmwareUpdate(LwM2mClient client, String newFwTitle, String newFwVersion, Optional newFwUrl); + void onTargetFirmwareUpdate(LwM2mClient client, String newFwTitle, String newFwVersion, Optional newFwUrl, Optional newFwTag); - void onTargetSoftwareUpdate(LwM2mClient client, String newSwTitle, String newSwVersion, Optional newSwUrl); + void onTargetSoftwareUpdate(LwM2mClient client, String newSwTitle, String newSwVersion, Optional newSwUrl, Optional newSwTag); void onCurrentFirmwareNameUpdate(LwM2mClient client, String name); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java index 5498752151..89709da405 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java +++ b/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.ConditionalOnProperty; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.TbTransportService; import javax.annotation.PostConstruct; @@ -114,6 +115,6 @@ public class MqttTransportService implements TbTransportService { @Override public String getName() { - return "MQTT"; + return DataConstants.MQTT_TRANSPORT_NAME; } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 3c296cc4ae..d0cee4a329 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/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.stereotype.Service; 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.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; @@ -300,7 +301,7 @@ public class SnmpTransportService implements TbTransportService { @Override public String getName() { - return "SNMP"; + return DataConstants.SNMP_TRANSPORT_NAME; } @PreDestroy diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java index 644da7f4ec..156cff5f7d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java +++ b/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.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; +import org.thingsboard.server.gen.transport.TransportProtos.UplinkNotificationMsg; import java.util.Optional; import java.util.UUID; @@ -44,6 +45,8 @@ public interface SessionMsgListener { void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse); + default void onUplinkNotification(UplinkNotificationMsg notificationMsg){}; + default void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto toTransportUpdateCredentials){} default void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) {} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 9aff28d73f..5227f671bc 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/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.ValidateDeviceCredentialsResponse; 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.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; @@ -128,4 +129,6 @@ public interface TransportService { void deregisterSession(SessionInfoProto sessionInfo); void log(SessionInfoProto sessionInfo, String msg); + + void notifyAboutUplink(SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg build, TransportServiceCallback empty); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index a1503e86be..eda114e498 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/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 callback) { + if (checkLimits(sessionInfo, msg, callback)) { + reportActivityInternal(sessionInfo); + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setUplinkNotificationMsg(msg).build(), callback); + } + } + @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcRequestMsg msg, boolean isFailedRpc, TransportServiceCallback callback) { if (msg.getPersisted()) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 178bf0b148..525630fc27 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/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_TILE_COLUMN = TITLE_PROPERTY; 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_FILE_NAME_COLUMN = "file_name"; public static final String OTA_PACKAGE_CONTENT_TYPE_COLUMN = "content_type"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java index a5291e8a79..7e6f9a2aff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java +++ b/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_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_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_TILE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN; @@ -78,6 +79,9 @@ public class OtaPackageEntity extends BaseSqlEntity implements Searc @Column(name = OTA_PACKAGE_VERSION_COLUMN) private String version; + @Column(name = OTA_PACKAGE_TAG_COLUMN) + private String tag; + @Column(name = OTA_PACKAGE_URL_COLUMN) private String url; @@ -112,24 +116,25 @@ public class OtaPackageEntity extends BaseSqlEntity implements Searc super(); } - public OtaPackageEntity(OtaPackage firmware) { - this.createdTime = firmware.getCreatedTime(); - this.setUuid(firmware.getUuidId()); - this.tenantId = firmware.getTenantId().getId(); - if (firmware.getDeviceProfileId() != null) { - this.deviceProfileId = firmware.getDeviceProfileId().getId(); + public OtaPackageEntity(OtaPackage otaPackage) { + this.createdTime = otaPackage.getCreatedTime(); + this.setUuid(otaPackage.getUuidId()); + this.tenantId = otaPackage.getTenantId().getId(); + if (otaPackage.getDeviceProfileId() != null) { + this.deviceProfileId = otaPackage.getDeviceProfileId().getId(); } - this.type = firmware.getType(); - this.title = firmware.getTitle(); - this.version = firmware.getVersion(); - this.url = firmware.getUrl(); - this.fileName = firmware.getFileName(); - this.contentType = firmware.getContentType(); - this.checksumAlgorithm = firmware.getChecksumAlgorithm(); - this.checksum = firmware.getChecksum(); - this.data = firmware.getData().array(); - this.dataSize = firmware.getDataSize(); - this.additionalInfo = firmware.getAdditionalInfo(); + this.type = otaPackage.getType(); + this.title = otaPackage.getTitle(); + this.version = otaPackage.getVersion(); + this.tag = otaPackage.getTag(); + this.url = otaPackage.getUrl(); + this.fileName = otaPackage.getFileName(); + this.contentType = otaPackage.getContentType(); + this.checksumAlgorithm = otaPackage.getChecksumAlgorithm(); + this.checksum = otaPackage.getChecksum(); + this.data = otaPackage.getData().array(); + this.dataSize = otaPackage.getDataSize(); + this.additionalInfo = otaPackage.getAdditionalInfo(); } @Override @@ -144,26 +149,27 @@ public class OtaPackageEntity extends BaseSqlEntity implements Searc @Override public OtaPackage toData() { - OtaPackage firmware = new OtaPackage(new OtaPackageId(id)); - firmware.setCreatedTime(createdTime); - firmware.setTenantId(new TenantId(tenantId)); + OtaPackage otaPackage = new OtaPackage(new OtaPackageId(id)); + otaPackage.setCreatedTime(createdTime); + otaPackage.setTenantId(new TenantId(tenantId)); if (deviceProfileId != null) { - firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); + otaPackage.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); } - firmware.setType(type); - firmware.setTitle(title); - firmware.setVersion(version); - firmware.setUrl(url); - firmware.setFileName(fileName); - firmware.setContentType(contentType); - firmware.setChecksumAlgorithm(checksumAlgorithm); - firmware.setChecksum(checksum); - firmware.setDataSize(dataSize); + otaPackage.setType(type); + otaPackage.setTitle(title); + otaPackage.setVersion(version); + otaPackage.setTag(tag); + otaPackage.setUrl(url); + otaPackage.setFileName(fileName); + otaPackage.setContentType(contentType); + otaPackage.setChecksumAlgorithm(checksumAlgorithm); + otaPackage.setChecksum(checksum); + otaPackage.setDataSize(dataSize); if (data != null) { - firmware.setData(ByteBuffer.wrap(data)); - firmware.setHasData(true); + otaPackage.setData(ByteBuffer.wrap(data)); + otaPackage.setHasData(true); } - firmware.setAdditionalInfo(additionalInfo); - return firmware; + otaPackage.setAdditionalInfo(additionalInfo); + return otaPackage; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java index db16251f71..b4909a6d2d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java +++ b/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_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_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_TILE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN; @@ -78,6 +79,9 @@ public class OtaPackageInfoEntity extends BaseSqlEntity implemen @Column(name = OTA_PACKAGE_VERSION_COLUMN) private String version; + @Column(name = OTA_PACKAGE_TAG_COLUMN) + private String tag; + @Column(name = OTA_PACKAGE_URL_COLUMN) private String url; @@ -111,26 +115,27 @@ public class OtaPackageInfoEntity extends BaseSqlEntity implemen super(); } - public OtaPackageInfoEntity(OtaPackageInfo firmware) { - this.createdTime = firmware.getCreatedTime(); - this.setUuid(firmware.getUuidId()); - this.tenantId = firmware.getTenantId().getId(); - this.type = firmware.getType(); - if (firmware.getDeviceProfileId() != null) { - this.deviceProfileId = firmware.getDeviceProfileId().getId(); + public OtaPackageInfoEntity(OtaPackageInfo otaPackageInfo) { + this.createdTime = otaPackageInfo.getCreatedTime(); + this.setUuid(otaPackageInfo.getUuidId()); + this.tenantId = otaPackageInfo.getTenantId().getId(); + this.type = otaPackageInfo.getType(); + if (otaPackageInfo.getDeviceProfileId() != null) { + this.deviceProfileId = otaPackageInfo.getDeviceProfileId().getId(); } - this.title = firmware.getTitle(); - this.version = firmware.getVersion(); - this.url = firmware.getUrl(); - this.fileName = firmware.getFileName(); - this.contentType = firmware.getContentType(); - this.checksumAlgorithm = firmware.getChecksumAlgorithm(); - this.checksum = firmware.getChecksum(); - this.dataSize = firmware.getDataSize(); - this.additionalInfo = firmware.getAdditionalInfo(); + this.title = otaPackageInfo.getTitle(); + this.version = otaPackageInfo.getVersion(); + this.tag = otaPackageInfo.getTag(); + this.url = otaPackageInfo.getUrl(); + this.fileName = otaPackageInfo.getFileName(); + this.contentType = otaPackageInfo.getContentType(); + this.checksumAlgorithm = otaPackageInfo.getChecksumAlgorithm(); + this.checksum = otaPackageInfo.getChecksum(); + 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, Object additionalInfo, boolean hasData) { this.id = id; @@ -140,6 +145,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity implemen this.type = type; this.title = title; this.version = version; + this.tag = tag; this.url = url; this.fileName = fileName; this.contentType = contentType; @@ -162,23 +168,24 @@ public class OtaPackageInfoEntity extends BaseSqlEntity implemen @Override public OtaPackageInfo toData() { - OtaPackageInfo firmware = new OtaPackageInfo(new OtaPackageId(id)); - firmware.setCreatedTime(createdTime); - firmware.setTenantId(new TenantId(tenantId)); + OtaPackageInfo otaPackageInfo = new OtaPackageInfo(new OtaPackageId(id)); + otaPackageInfo.setCreatedTime(createdTime); + otaPackageInfo.setTenantId(new TenantId(tenantId)); if (deviceProfileId != null) { - firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); + otaPackageInfo.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); } - firmware.setType(type); - firmware.setTitle(title); - firmware.setVersion(version); - firmware.setUrl(url); - firmware.setFileName(fileName); - firmware.setContentType(contentType); - firmware.setChecksumAlgorithm(checksumAlgorithm); - firmware.setChecksum(checksum); - firmware.setDataSize(dataSize); - firmware.setAdditionalInfo(additionalInfo); - firmware.setHasData(hasData); - return firmware; + otaPackageInfo.setType(type); + otaPackageInfo.setTitle(title); + otaPackageInfo.setVersion(version); + otaPackageInfo.setTag(tag); + otaPackageInfo.setUrl(url); + otaPackageInfo.setFileName(fileName); + otaPackageInfo.setContentType(contentType); + otaPackageInfo.setChecksumAlgorithm(checksumAlgorithm); + otaPackageInfo.setChecksum(checksum); + otaPackageInfo.setDataSize(dataSize); + otaPackageInfo.setAdditionalInfo(additionalInfo); + otaPackageInfo.setHasData(hasData); + return otaPackageInfo; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java index b9942ec6e6..5b034f7d00 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java +++ b/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.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; 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!"); } + if (!Objects.equals(otaPackage.getTag(), otaPackageOld.getTag())) { + throw new DataValidationException("Updating otaPackage tag is prohibited!"); + } + if (!otaPackageOld.getDeviceProfileId().equals(otaPackage.getDeviceProfileId())) { throw new DataValidationException("Updating otaPackage deviceProfile is prohibited!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java index b380f8a150..112b6dd222 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java +++ b/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; public interface OtaPackageInfoRepository extends CrudRepository { - @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 " + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") Page findAllByTenantId(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, 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 " + "AND f.deviceProfileId = :deviceProfileId " + "AND f.type = :type " + @@ -45,7 +45,7 @@ public interface OtaPackageInfoRepository extends CrudRepository--> - - - - - - - - + + + @@ -194,13 +189,12 @@ -
+
+ [ngModel]="configurationValue">
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index 0934a9b6e2..a6e57abe4f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/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. /// -import { DeviceProfileTransportConfiguration } from '@shared/models/device.models'; import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { ControlValueAccessor, @@ -76,7 +75,6 @@ import { takeUntil } from 'rxjs/operators'; }) export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validator, OnDestroy { - private configurationValue: Lwm2mProfileConfigModels; private requiredValue: boolean; private disabled = false; private destroy$ = new Subject(); @@ -84,7 +82,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro bindingModeTypes = Object.values(BingingMode); bindingModeTypeNamesMap = BingingModeTranslationsMap; lwm2mDeviceProfileFormGroup: FormGroup; - lwm2mDeviceConfigFormGroup: FormGroup; + configurationValue: Lwm2mProfileConfigModels; sortFunction: (key: string, value: object) => object; get required(): boolean { @@ -128,9 +126,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro compositeOperationsSupport: [false] }) }); - this.lwm2mDeviceConfigFormGroup = this.fb.group({ - configurationJson: [null, Validators.required] - }); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((fwStrategy) => { @@ -158,11 +153,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro ).subscribe((value) => { this.updateDeviceProfileValue(value); }); - this.lwm2mDeviceConfigFormGroup.valueChanges.pipe( - takeUntil(this.destroy$) - ).subscribe(() => { - this.updateModel(); - }); this.sortFunction = this.sortObjectKeyPathJson; } @@ -182,10 +172,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.disabled = isDisabled; if (isDisabled) { this.lwm2mDeviceProfileFormGroup.disable({emitEvent: false}); - this.lwm2mDeviceConfigFormGroup.disable({emitEvent: false}); } else { this.lwm2mDeviceProfileFormGroup.enable({emitEvent: false}); - this.lwm2mDeviceConfigFormGroup.enable({emitEvent: false}); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.powerMode').updateValueAndValidity({onlySelf: true}); this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').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)) { this.configurationValue = value; const defaultFormSettings = !(value.observeAttr.attribute.length && value.observeAttr.telemetry.length); - this.lwm2mDeviceConfigFormGroup.patchValue({ - configurationJson: this.configurationValue - }, {emitEvent: defaultFormSettings}); if (defaultFormSettings) { await this.defaultProfileConfig(); } @@ -227,9 +212,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.configurationValue.bootstrap.bootstrapServer = bootstrap; this.configurationValue.bootstrap.lwm2mServer = lwm2m; - this.lwm2mDeviceConfigFormGroup.patchValue({ - configurationJson: this.configurationValue - }, {emitEvent: false}); this.lwm2mDeviceProfileFormGroup.patchValue({ bootstrap: this.configurationValue.bootstrap }, {emitEvent: false}); @@ -265,6 +247,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro swUpdateResource: this.configurationValue.clientLwM2mSettings.swUpdateResource || '', powerMode: this.configurationValue.clientLwM2mSettings.powerMode || PowerMode.DRX, edrxCycle: this.configurationValue.clientLwM2mSettings.edrxCycle || 0, + pagingTransmissionWindow: this.configurationValue.clientLwM2mSettings.pagingTransmissionWindow || 0, + psmActivityTimer: this.configurationValue.clientLwM2mSettings.psmActivityTimer || 0, compositeOperationsSupport: this.configurationValue.clientLwM2mSettings.compositeOperationsSupport || false } }, @@ -277,9 +261,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private updateModel = (): void => { - let configuration: DeviceProfileTransportConfiguration = null; - if (this.lwm2mDeviceConfigFormGroup.valid && this.lwm2mDeviceProfileFormGroup.valid) { - configuration = this.lwm2mDeviceConfigFormGroup.value.configurationJson; + let configuration: Lwm2mProfileConfigModels = null; + if (this.lwm2mDeviceProfileFormGroup.valid) { + configuration = this.configurationValue; } this.propagateChange(configuration); } @@ -299,7 +283,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.configurationValue.bootstrap.lwm2mServer = config.bootstrap.lwm2mServer; this.configurationValue.bootstrap.servers = config.bootstrap.servers; this.configurationValue.clientLwM2mSettings = config.clientLwM2mSettings; - this.upDateJsonAllConfig(); this.updateModel(); } @@ -327,7 +310,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } if (isDefinedAndNotNull(keyNameJson)) { this.configurationValue.observeAttr.keyName = this.validateKeyNameObjects(keyNameJson, attributeArray, telemetryArray); - this.upDateJsonAllConfig(); this.updateKeyNameObjects(objectLwM2MS); } } @@ -513,12 +495,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro return (objectsIds.size > 0) ? Array.from(objectsIds) : []; } - private upDateJsonAllConfig = (): void => { - this.lwm2mDeviceConfigFormGroup.patchValue({ - configurationJson: this.configurationValue - }, {emitEvent: false}); - } - addObjectsList = (value: ObjectLwM2M[]): void => { this.updateObserveAttrTelemetryObjectFormGroup(value); } @@ -536,7 +512,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.removeKeyNameFromJson(value.keyId); this.removeAttributesFromJson(value.keyId); this.updateObserveAttrTelemetryObjectFormGroup(objectsOld); - this.upDateJsonAllConfig(); } private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, keyId: string): void => { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.html new file mode 100644 index 0000000000..bebc9e09a4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.html @@ -0,0 +1,21 @@ + +
+ +
entity.no-data
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts new file mode 100644 index 0000000000..ba8ef3b3e8 --- /dev/null +++ b/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; + + constructor(protected store: Store, + 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; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index b55c21b179..475d2c7170 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/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 { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-overview-widget.component'; import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input-widget.component'; +import { QrCodeWidgetComponent } from '@home/components/widget/lib/qrcode-widget.component'; @NgModule({ declarations: @@ -58,7 +59,8 @@ import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input PhotoCameraInputWidgetComponent, GatewayFormComponent, NavigationCardsWidgetComponent, - NavigationCardWidgetComponent + NavigationCardWidgetComponent, + QrCodeWidgetComponent ], imports: [ CommonModule, @@ -80,7 +82,8 @@ import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input PhotoCameraInputWidgetComponent, GatewayFormComponent, NavigationCardsWidgetComponent, - NavigationCardWidgetComponent + NavigationCardWidgetComponent, + QrCodeWidgetComponent ], providers: [ CustomDialogService, diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts index 0f542bec8a..ef730bf4a6 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts @@ -59,9 +59,10 @@ export class OtaUpdateTableConfigResolve implements Resolve('createdTime', 'common.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'ota-update.title', '20%'), - new EntityTableColumn('version', 'ota-update.version', '20%'), - new EntityTableColumn('type', 'ota-update.package-type', '20%', entity => { + new EntityTableColumn('title', 'ota-update.title', '15%'), + new EntityTableColumn('version', 'ota-update.version', '15%'), + new EntityTableColumn('tag', 'ota-update.version-tag', '15%'), + new EntityTableColumn('type', 'ota-update.package-type', '15%', entity => { return this.translate.instant(OtaUpdateTypeTranslationMap.get(entity.type)); }), new EntityTableColumn('url', 'ota-update.direct-url', '20%', entity => { diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html index be3a2ead31..fa6e0428eb 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html @@ -74,6 +74,11 @@ + + ota-update.version-tag + + ota-update.version-tag-hint +
ota-update.warning-after-save-no-edit
- Upload binary file - Use external URL + {{ "ota-update.upload-binary-file" | translate }} + {{ "ota-update.use-external-url" | translate }}
diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts index 9fd51302e1..7ea9ef2e3b 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts +++ b/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 { Subject } from 'rxjs'; +import { combineLatest, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { TranslateService } from '@ngx-translate/core'; @@ -30,7 +30,7 @@ import { OtaUpdateTypeTranslationMap } from '@shared/models/ota-package.models'; 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'; @Component({ @@ -56,22 +56,33 @@ export class OtaUpdateComponent extends EntityComponent implements O ngOnInit() { super.ngOnInit(); - this.entityForm.get('isURL').valueChanges.pipe( - filter(() => this.isAdd), - takeUntil(this.destroy$) - ).subscribe((isURL) => { - if (isURL === false) { - this.entityForm.get('url').clearValidators(); - this.entityForm.get('file').setValidators(Validators.required); - this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); - this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); - } else { - this.entityForm.get('file').clearValidators(); - this.entityForm.get('url').setValidators([Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]); - this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); - this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); - } - }); + if (this.isAdd) { + this.entityForm.get('isURL').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((isURL) => { + if (isURL === false) { + this.entityForm.get('url').clearValidators(); + this.entityForm.get('file').setValidators(Validators.required); + this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); + this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); + } else { + this.entityForm.get('file').clearValidators(); + this.entityForm.get('url').setValidators([Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]); + this.entityForm.get('file').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() { @@ -92,6 +103,7 @@ export class OtaUpdateComponent extends EntityComponent implements O const form = this.fb.group({ title: [entity ? entity.title : '', [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], deviceProfileId: [entity ? entity.deviceProfileId : null, Validators.required], checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256], @@ -119,6 +131,7 @@ export class OtaUpdateComponent extends EntityComponent implements O this.entityForm.patchValue({ title: entity.title, version: entity.version, + tag: entity.tag, type: entity.type, deviceProfileId: entity.deviceProfileId, checksumAlgorithm: entity.checksumAlgorithm, diff --git a/ui-ngx/src/app/shared/models/ota-package.models.ts b/ui-ngx/src/app/shared/models/ota-package.models.ts index a27330235a..f91664612a 100644 --- a/ui-ngx/src/app/shared/models/ota-package.models.ts +++ b/ui-ngx/src/app/shared/models/ota-package.models.ts @@ -91,6 +91,7 @@ export interface OtaPackageInfo extends BaseData { deviceProfileId?: DeviceProfileId; title?: string; version?: string; + tag?: string; hasData?: boolean; url?: string; fileName: string; diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 35892cfede..3b96068406 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -1236,14 +1236,12 @@ "object-list": "SEznam objektů", "object-list-empty": "Žádné objekty nebyly vybrány.", "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", "add-new-instances": "Přidat nové instance", "instances-list": "Seznam instancí", - "instances-input": "Vstupní hodnota Id instance", - "instances-input-holder": "Vstupní číslo instance...", + "instances-list-required": "Seznam instancí je povinný", + "instance-id-pattern": "Instance číslo musí být kladné číslo.", + "instance-id-max": "Maximální instance číslo hodnota {{max}}.", "instance": "Instance", "resource-label": "Název zdroje #ID", "observe-label": "Pozorování", @@ -1266,7 +1264,6 @@ "view-attribute": "Zobrazit atribut", "remove-attribute": "Odebrat atribut", "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", "short-id": "Krátké ID", "short-id-required": "Krátké ID je povinné.", @@ -1316,8 +1313,8 @@ "others-tab": "Ostatní nastavení", "client-strategy": "Strategie klienta při připojování", "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-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-only-observe": "Klientovi je odeslán pouze observe požadavek po úvodním spojení", + "client-strategy-read-all": "Načti všechny zdroje a observer požadavky na klienta po registraci", "fw-update": "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)", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c7c9a8302e..5ba2d2c808 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1273,7 +1273,6 @@ "view-attribute": "View attribute", "remove-attribute": "Remove attribute", "mode": "Security config mode", - "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }", "servers": "Servers", "short-id": "Short ID", "short-id-required": "Short ID is required.", @@ -1323,8 +1322,8 @@ "others-tab": "Other settings", "client-strategy": "Client strategy when connecting", "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-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-only-observe": "Only Observe Request to the client after the initial connection", + "client-strategy-read-all": "Read All Resources & Observe Request to the client after registration", "fw-update": "Firmware update", "fw-update-strategy": "Firmware update strategy", "fw-update-strategy-data": "Push firmware update as binary file using Object 19 and Resource 0 (Data)", @@ -2342,8 +2341,12 @@ "firmware": "Firmware", "software": "Software" }, + "upload-binary-file": "Upload binary file", + "use-external-url": "Use external URL", "version": "Version", "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." }, "position": { diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 1927f2e80b..3038f57bab 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -2774,7 +2774,25 @@ browserstack@^1.5.1: dependencies: 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" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== @@ -2798,7 +2816,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.5.0: +buffer@^5.4.3, buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -3953,6 +3971,11 @@ diffie-hellman@^5.0.0: miller-rabin "^4.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: version "3.0.1" 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" 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: version "4.0.6" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" @@ -7503,6 +7531,11 @@ pkg-dir@^4.1.0: dependencies: 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: version "1.6.4" 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" 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: version "6.7.0" 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" integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== -yargs@^13.3.2: +yargs@^13.2.4, yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==