diff --git a/application/src/main/java/org/thingsboard/server/service/gateway_device/DefaultGatewayDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/gateway_device/DefaultGatewayDeviceStateService.java index f3aee5a109..57a0b36508 100644 --- a/application/src/main/java/org/thingsboard/server/service/gateway_device/DefaultGatewayDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/gateway_device/DefaultGatewayDeviceStateService.java @@ -1,6 +1,22 @@ +/** + * 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. + */ package org.thingsboard.server.service.gateway_device; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.FutureCallback; @@ -15,6 +31,7 @@ import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; @@ -23,11 +40,11 @@ import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -39,8 +56,11 @@ public class DefaultGatewayDeviceStateService implements GatewayDeviceStateServi private static final String RENAMED_GATEWAY_DEVICES = "renamedGatewayDevices"; private static final String DELETED_GATEWAY_DEVICES = "deletedGatewayDevices"; + private static final String HANDLE_DEVICE_RENAMING_PARAMETER = "handleDeviceRenaming"; + private final AttributesService attributesService; private final RelationService relationService; + private final DeviceService deviceService; @Lazy @Autowired private TelemetrySubscriptionService tsSubService; @@ -49,44 +69,50 @@ public class DefaultGatewayDeviceStateService implements GatewayDeviceStateServi public void update(Device device, Device oldDevice) { List relationToGatewayList = relationService.findByFromAndType(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.LAST_CONNECTED_GATEWAY, RelationTypeGroup.COMMON); if (!relationToGatewayList.isEmpty()) { - EntityRelation relationToGateway = relationToGatewayList.get(0); - ListenableFuture> renamedGatewayDevicesFuture = attributesService.find(device.getTenantId(), relationToGateway.getTo(), DataConstants.SHARED_SCOPE, Collections.singletonList("renamedGatewayDevices")); - DonAsynchron.withCallback(renamedGatewayDevicesFuture, renamedGatewayDevicesList -> { - ObjectNode renamedGatewayDevicesNode; - KvEntry renamedGatewayDevicesKvEntry; - String newDeviceName = device.getName(); - String oldDeviceName = oldDevice.getName(); + if (oldDevice != null) { + EntityRelation relationToGateway = relationToGatewayList.get(0); - if (renamedGatewayDevicesList.isEmpty()) { - renamedGatewayDevicesNode = JacksonUtil.newObjectNode(); - renamedGatewayDevicesNode.put(oldDeviceName, newDeviceName); - } else { - AttributeKvEntry receivedRenamedGatewayDevicesAttribute = renamedGatewayDevicesList.get(0); - renamedGatewayDevicesNode = (ObjectNode) JacksonUtil.toJsonNode(receivedRenamedGatewayDevicesAttribute.getValueAsString()); - if (renamedGatewayDevicesNode.findValue(newDeviceName) != null && oldDeviceName.equals(renamedGatewayDevicesNode.get(newDeviceName).asText())) { - // If a new device name is the same like the first name or another device was renamed like some existing device - renamedGatewayDevicesNode.remove(newDeviceName); - } else { + Device gatewayDevice = deviceService.findDeviceById(device.getTenantId(), (DeviceId) relationToGateway.getTo()); + if (isHandleDeviceRenamingEnabled(gatewayDevice.getAdditionalInfo())) { + ListenableFuture> renamedGatewayDevicesFuture = attributesService.find(device.getTenantId(), relationToGateway.getTo(), DataConstants.SHARED_SCOPE, Collections.singletonList("renamedGatewayDevices")); + DonAsynchron.withCallback(renamedGatewayDevicesFuture, renamedGatewayDevicesList -> { + ObjectNode renamedGatewayDevicesNode; + KvEntry renamedGatewayDevicesKvEntry; + String newDeviceName = device.getName(); + String oldDeviceName = oldDevice.getName(); - AtomicBoolean renamedFirstTime = new AtomicBoolean(true); - - renamedGatewayDevicesNode.fields().forEachRemaining(entry -> { - // If device was renamed earlier - if (oldDeviceName.equals(entry.getValue().asText())) { - renamedGatewayDevicesNode.put(entry.getKey(), newDeviceName); - renamedFirstTime.set(false); - } - }); - if (renamedFirstTime.get()) { + if (renamedGatewayDevicesList.isEmpty()) { + renamedGatewayDevicesNode = JacksonUtil.newObjectNode(); renamedGatewayDevicesNode.put(oldDeviceName, newDeviceName); - } - } - } + } else { + AttributeKvEntry receivedRenamedGatewayDevicesAttribute = renamedGatewayDevicesList.get(0); + renamedGatewayDevicesNode = (ObjectNode) JacksonUtil.toJsonNode(receivedRenamedGatewayDevicesAttribute.getValueAsString()); + if (renamedGatewayDevicesNode.findValue(newDeviceName) != null && oldDeviceName.equals(renamedGatewayDevicesNode.get(newDeviceName).asText())) { + // If a new device name is the same like the first name or another device was renamed like some existing device + renamedGatewayDevicesNode.remove(newDeviceName); + } else { - renamedGatewayDevicesKvEntry = new JsonDataEntry(RENAMED_GATEWAY_DEVICES, JacksonUtil.toString(renamedGatewayDevicesNode)); - saveGatewayDevicesAttribute(device, relationToGateway, renamedGatewayDevicesKvEntry); - }, - e -> log.error("Cannot get gateway renamed devices attribute", e)); + AtomicBoolean renamedFirstTime = new AtomicBoolean(true); + + renamedGatewayDevicesNode.fields().forEachRemaining(entry -> { + // If device was renamed earlier + if (oldDeviceName.equals(entry.getValue().asText())) { + renamedGatewayDevicesNode.put(entry.getKey(), newDeviceName); + renamedFirstTime.set(false); + } + }); + if (renamedFirstTime.get()) { + renamedGatewayDevicesNode.put(oldDeviceName, newDeviceName); + } + } + } + + renamedGatewayDevicesKvEntry = new JsonDataEntry(RENAMED_GATEWAY_DEVICES, JacksonUtil.toString(renamedGatewayDevicesNode)); + saveGatewayDevicesAttribute(device, relationToGateway, renamedGatewayDevicesKvEntry); + }, + e -> log.error("Cannot get gateway renamed devices attribute", e)); + } + } } } @@ -105,17 +131,17 @@ public class DefaultGatewayDeviceStateService implements GatewayDeviceStateServi if (renamedGatewayDevicesNode.findValue(deletedDeviceName[0]) != null) { renamedGatewayDevicesNode.remove(deletedDeviceName[0]); renamedListChanged.set(true); - } else { - Map renamedGatewayDevicesMap = JacksonUtil.OBJECT_MAPPER.convertValue(renamedGatewayDevicesNode, new TypeReference<>() {}); - renamedGatewayDevicesMap.forEach((key, value) -> { - // If device was renamed earlier - if (deletedDeviceName[0].equals(value)) { - renamedGatewayDevicesNode.remove(key); - deletedDeviceName[0] = key; - renamedListChanged.set(true); - } - }); } + Map renamedGatewayDevicesMap = JacksonUtil.OBJECT_MAPPER.convertValue(renamedGatewayDevicesNode, new TypeReference<>() { + }); + renamedGatewayDevicesMap.forEach((key, value) -> { + // If device was renamed earlier + if (deletedDeviceName[0].equals(value)) { + renamedGatewayDevicesNode.remove(key); + deletedDeviceName[0] = key; + renamedListChanged.set(true); + } + }); if (renamedListChanged.get()) { KvEntry renamedGatewayDevicesKvEntry = new JsonDataEntry(RENAMED_GATEWAY_DEVICES, JacksonUtil.toString(renamedGatewayDevicesNode)); saveGatewayDevicesAttribute(device, relationToGateway, renamedGatewayDevicesKvEntry); @@ -138,6 +164,32 @@ public class DefaultGatewayDeviceStateService implements GatewayDeviceStateServi } } + @Override + public void checkAndUpdateDeletedGatewayDevicesAttribute(Device device) { + List relationToGatewayList = relationService.findByFromAndType(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.LAST_CONNECTED_GATEWAY, RelationTypeGroup.COMMON); + if (!relationToGatewayList.isEmpty()) { + EntityRelation relationToGateway = relationToGatewayList.get(0); + ListenableFuture> deletedGatewayDevicesFuture = attributesService.find(device.getTenantId(), relationToGateway.getTo(), DataConstants.SHARED_SCOPE, Collections.singletonList("deletedGatewayDevices")); + DonAsynchron.withCallback(deletedGatewayDevicesFuture, deletedGatewayDevicesList -> { + ArrayNode deletedGatewayDevicesNode; + if (!deletedGatewayDevicesList.isEmpty()) { + int deletedDeviceIndex = -1; + deletedGatewayDevicesNode = (ArrayNode) JacksonUtil.toJsonNode(deletedGatewayDevicesList.get(0).getValueAsString()); + for (int i = 0; i < deletedGatewayDevicesNode.size(); i++) { + if (deletedGatewayDevicesNode.get(i).asText().equals(device.getName())) { + deletedDeviceIndex = i; + } + } + if (deletedDeviceIndex != -1) { + deletedGatewayDevicesNode.remove(deletedDeviceIndex); + KvEntry deletedGatewayDevicesKvEntry = new JsonDataEntry(DELETED_GATEWAY_DEVICES, JacksonUtil.toString(deletedGatewayDevicesNode)); + saveGatewayDevicesAttribute(device, relationToGateway, deletedGatewayDevicesKvEntry); + } + } + }, e -> log.error("Cannot get gateway deleted devices attribute", e)); + } + } + private void saveGatewayDevicesAttribute(Device device, EntityRelation relationToGateway, KvEntry gatewayDevicesKvEntry) { AttributeKvEntry attrKvEntry = new BaseAttributeKvEntry(System.currentTimeMillis(), gatewayDevicesKvEntry); tsSubService.saveAndNotify(device.getTenantId(), relationToGateway.getTo(), DataConstants.SHARED_SCOPE, List.of(attrKvEntry), true, new FutureCallback() { @@ -152,4 +204,11 @@ public class DefaultGatewayDeviceStateService implements GatewayDeviceStateServi } }); } + + private boolean isHandleDeviceRenamingEnabled(JsonNode additionalInfo) { + if (additionalInfo.get(HANDLE_DEVICE_RENAMING_PARAMETER) != null) { + return additionalInfo.get(HANDLE_DEVICE_RENAMING_PARAMETER).asBoolean(); + } + return false; + } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/gateway_device/GatewayDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/gateway_device/GatewayDeviceStateService.java index f086b25d5a..d4079487a5 100644 --- a/application/src/main/java/org/thingsboard/server/service/gateway_device/GatewayDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/gateway_device/GatewayDeviceStateService.java @@ -1,3 +1,18 @@ +/** + * 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. + */ package org.thingsboard.server.service.gateway_device; import org.thingsboard.server.common.data.Device; @@ -7,4 +22,6 @@ public interface GatewayDeviceStateService { void update(Device device, Device oldDevice); void delete(Device device); + + void checkAndUpdateDeletedGatewayDevicesAttribute(Device device); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index cb4e01032f..929d87e5f4 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -95,6 +95,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.gateway_device.GatewayDeviceStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.resource.TbResourceService; @@ -137,6 +138,7 @@ public class DefaultTransportApiService implements TransportApiService { private final TbResourceService resourceService; private final OtaPackageService otaPackageService; private final OtaPackageDataCache otaPackageDataCache; + private final GatewayDeviceStateService gatewayDeviceStateService; private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); @@ -317,6 +319,8 @@ public class DefaultTransportApiService implements TransportApiService { } relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, lastConnectedGatewayRelation); + gatewayDeviceStateService.checkAndUpdateDeletedGatewayDevicesAttribute(device); + GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder() .setDeviceInfo(getDeviceInfoProto(device)); DeviceProfile deviceProfile = deviceProfileCache.get(device.getTenantId(), device.getDeviceProfileId()); diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index ae0be158d5..cac8ae4c1e 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -103,10 +103,16 @@ {{ 'device.is-gateway' | translate }} + +
{{ 'device.overwrite-activity-time' | translate }} + + {{ 'device.handle-device-renaming' | translate }} +
device.description diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index fdbc25107f..552e1d5eb8 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -109,6 +109,7 @@ export class DeviceWizardDialogComponent extends label: ['', Validators.maxLength(255)], gateway: [false], overwriteActivityTime: [false], + handleDeviceRenaming: [false], addProfileType: [0], deviceProfileId: [null, Validators.required], newDeviceProfileTitle: [{value: null, disabled: true}], @@ -326,6 +327,7 @@ export class DeviceWizardDialogComponent extends additionalInfo: { gateway: this.deviceWizardFormGroup.get('gateway').value, overwriteActivityTime: this.deviceWizardFormGroup.get('overwriteActivityTime').value, + handleDeviceRenaming: this.deviceWizardFormGroup.get('handleDeviceRenaming').value, description: this.deviceWizardFormGroup.get('description').value }, customerId: null diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.html b/ui-ngx/src/app/modules/home/pages/device/device.component.html index 1187a34f5d..1f89481fd0 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -128,10 +128,16 @@ {{ 'device.is-gateway' | translate }} + +
{{ 'device.overwrite-activity-time' | translate }} + + {{ 'device.handle-device-renaming' | translate }} +
device.description diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index 7cd60a89f4..cd1c78b3dd 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -92,6 +92,7 @@ export class DeviceComponent extends EntityComponent { { gateway: [entity && entity.additionalInfo ? entity.additionalInfo.gateway : false], overwriteActivityTime: [entity && entity.additionalInfo ? entity.additionalInfo.overwriteActivityTime : false], + handleDeviceRenaming: [entity && entity.additionalInfo ? entity.additionalInfo.handleDeviceRenaming : false], description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], } ) @@ -121,6 +122,7 @@ export class DeviceComponent extends EntityComponent { additionalInfo: { gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false, overwriteActivityTime: entity.additionalInfo ? entity.additionalInfo.overwriteActivityTime : false, + handleDeviceRenaming: entity.additionalInfo ? entity.additionalInfo.handleDeviceRenaming : false, description: entity.additionalInfo ? entity.additionalInfo.description : '' } }); 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 3982bfedda..bfa105092f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1042,6 +1042,7 @@ "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", "is-gateway": "Is gateway", "overwrite-activity-time": "Overwrite activity time for connected device", + "handle-device-renaming": "Handle device renaming", "public": "Public", "device-public": "Device is public", "select-device": "Select device",