diff --git a/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg b/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg index 3aee266486..f284c7f86d 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg @@ -93,7 +93,7 @@ }, "valueToData": { "type": "CONSTANT", - "constantValue": false, + "constantValue": true, "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" } }, @@ -127,7 +127,7 @@ }, "valueToData": { "type": "CONSTANT", - "constantValue": true, + "constantValue": false, "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" } }, 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 b7c43391be..51d37e291a 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 @@ -417,7 +417,8 @@ public class DefaultTransportApiService implements TransportApiService { requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), new ProvisionDeviceProfileCredentials( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), - requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()), + requestMsg.getGateway())); } catch (ProvisionFailedException e) { return getTransportApiResponseMsg(new DeviceCredentials(), TransportProtos.ResponseStatus.valueOf(e.getMessage())); } @@ -665,7 +666,7 @@ public class DefaultTransportApiService implements TransportApiService { private ProvisionRequest createProvisionRequest(String certificateValue) { return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue), - null); + null, null); } } diff --git a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java index 8d5d66f6d2..75b84f50be 100644 --- a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java @@ -236,7 +236,7 @@ public class DeviceProvisionServiceTest { private ProvisionRequest createProvisionRequest(String certificateValue) { return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE, new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue), - null); + null, null); } public static String certTrimNewLinesForChainInDeviceProfile(String input) { diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java index 22d1e9e349..b2187ba7b4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -67,6 +68,11 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { processTestProvisioningCreateNewDeviceWithoutCredentials(); } + @Test + public void testProvisioningCreateNewGatewayDevice() throws Exception { + processTestProvisioningCreateNewGatewayDevice(); + } + @Test public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { processTestProvisioningCreateNewDeviceWithAccessToken(); @@ -123,6 +129,37 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { } + private void processTestProvisioningCreateNewGatewayDevice() throws Exception { + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish(true)); + Assert.assertTrue(response.hasNonNull("credentialsType")); + Assert.assertTrue(response.hasNonNull("status")); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + JsonNode additionalInfo = createdDevice.getAdditionalInfo(); + Assert.assertNotNull(additionalInfo); + Assert.assertTrue(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()); + Assert.assertTrue(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText()); + } + + private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() .deviceName("Test Provision device3") @@ -222,16 +259,30 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { } private byte[] createCoapClientAndPublish() throws Exception { - return createCoapClientAndPublish(""); + return createCoapClientAndPublish(false); + } + + private byte[] createCoapClientAndPublish(boolean isGateway) throws Exception { + return createCoapClientAndPublish("", isGateway); } private byte[] createCoapClientAndPublish(String deviceCredentials) throws Exception { - String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); + return createCoapClientAndPublish(deviceCredentials, false); + } + + private byte[] createCoapClientAndPublish(String deviceCredentials, boolean isGateway) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceCredentials, isGateway); client = new CoapTestClient(accessToken, FeatureType.PROVISION); return client.postMethod(provisionRequestMsg.getBytes()).getPayload(); } - private String createTestProvisionMessage(String deviceCredentials) { - return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}"; + protected String createTestProvisionMessage(String deviceCredentials, boolean isGateway) { + String request = "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + + deviceCredentials; + if (isGateway) { + request += ",\"gateway\":true"; + } + request += "}"; + return request; } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java index 5a7c880d44..4b1f806f74 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.coap.provision; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapResponse; import org.junit.After; @@ -22,6 +23,7 @@ import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -74,6 +76,11 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { processTestProvisioningCreateNewDeviceWithoutCredentials(); } + @Test + public void testProvisioningCreateNewGatewayDevice() throws Exception { + processTestProvisioningCreateNewGatewayDevice(); + } + @Test public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { processTestProvisioningCreateNewDeviceWithAccessToken(); @@ -124,6 +131,35 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); } + private void processTestProvisioningCreateNewGatewayDevice() throws Exception { + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); + byte[] testsProvisionMessage = createTestsProvisionMessage(null, null, true); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(testsProvisionMessage)); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + JsonNode additionalInfo = createdDevice.getAdditionalInfo(); + Assert.assertNotNull(additionalInfo); + Assert.assertTrue(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()); + Assert.assertTrue(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().name()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); + } + private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() .deviceName("Test Provision device3") @@ -233,6 +269,10 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { } private byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { + return createTestsProvisionMessage(credentialsType, credentialsData, false); + } + + private byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData, boolean isGateway) throws Exception { return ProvisionDeviceRequestMsg.newBuilder() .setDeviceName("Test Provision device") .setCredentialsType(credentialsType != null ? credentialsType : CredentialsType.ACCESS_TOKEN) @@ -241,7 +281,9 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { ProvisionDeviceCredentialsMsg.newBuilder() .setProvisionDeviceKey("testProvisionKey") .setProvisionDeviceSecret("testProvisionSecret") - ).build() + ) + .setGateway(isGateway) + .build() .toByteArray(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java index e82612963f..05080a9b30 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; 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.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -68,6 +69,11 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { processTestProvisioningCreateNewDeviceWithoutCredentials(); } + @Test + public void testProvisioningCreateNewGatewayDevice() throws Exception { + processTestProvisioningCreateNewGatewayDevice(); + } + @Test public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { processTestProvisioningCreateNewDeviceWithAccessToken(); @@ -130,6 +136,37 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { } + protected void processTestProvisioningCreateNewGatewayDevice() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + super.processBeforeTest(configProperties); + byte[] result = createMqttClientAndPublish(true); + JsonNode response = JacksonUtil.fromBytes(result); + Assert.assertTrue(response.hasNonNull("credentialsType")); + Assert.assertTrue(response.hasNonNull("status")); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + JsonNode additionalInfo = createdDevice.getAdditionalInfo(); + Assert.assertNotNull(additionalInfo); + Assert.assertTrue(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()); + Assert.assertTrue(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText()); + } + + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() .deviceName("Test Provision device3") @@ -264,11 +301,19 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { } protected byte[] createMqttClientAndPublish() throws Exception { - return createMqttClientAndPublish(""); + return createMqttClientAndPublish(false); + } + + protected byte[] createMqttClientAndPublish(boolean isGateway) throws Exception { + return createMqttClientAndPublish("", isGateway); } protected byte[] createMqttClientAndPublish(String deviceCredentials) throws Exception { - String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); + return createMqttClientAndPublish(deviceCredentials, false); + } + + protected byte[] createMqttClientAndPublish(String deviceCredentials, boolean isGateway) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceCredentials, isGateway); MqttTestClient client = new MqttTestClient(); client.connectAndWait("provision"); MqttTestCallback onProvisionCallback = new MqttTestSubscribeOnTopicCallback(DEVICE_PROVISION_RESPONSE_TOPIC); @@ -280,7 +325,12 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { return onProvisionCallback.getPayloadBytes(); } - protected String createTestProvisionMessage(String deviceCredentials) { - return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}"; + protected String createTestProvisionMessage(String deviceCredentials, boolean isGateway) { + String request = "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials; + if (isGateway) { + request += ",\"gateway\":true"; + } + request += "}"; + return request; } } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java index 2411d39004..3c1adfb6b4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java @@ -15,12 +15,14 @@ */ package org.thingsboard.server.transport.mqtt.mqttv3.provision; +import com.fasterxml.jackson.databind.JsonNode; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; 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.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -76,6 +78,11 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { processTestProvisioningCreateNewDeviceWithoutCredentials(); } + @Test + public void testProvisioningCreateNewGatewayDevice() throws Exception { + processTestProvisioningCreateNewGatewayDevice(); + } + @Test public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { processTestProvisioningCreateNewDeviceWithAccessToken(); @@ -130,6 +137,36 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); } + protected void processTestProvisioningCreateNewGatewayDevice() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); + + byte[] provisionRequestMsg = createTestsProvisionMessage(null, null, true); + byte[] responseBytesMsg = createMqttClientAndPublish(provisionRequestMsg); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(responseBytesMsg); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); + + Assert.assertNotNull(createdDevice); + + JsonNode additionalInfo = createdDevice.getAdditionalInfo(); + Assert.assertNotNull(additionalInfo); + Assert.assertTrue(additionalInfo.has(DataConstants.GATEWAY_PARAMETER) + && additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()); + Assert.assertTrue(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().name()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); + } + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() .deviceName("Test Provision device3") @@ -280,6 +317,10 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { } protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { + return createTestsProvisionMessage(credentialsType, credentialsData, false); + } + + protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData, boolean isGateway) throws Exception { return ProvisionDeviceRequestMsg.newBuilder() .setDeviceName("Test Provision device") .setCredentialsType(credentialsType != null ? credentialsType : CredentialsType.ACCESS_TOKEN) @@ -288,7 +329,9 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { ProvisionDeviceCredentialsMsg.newBuilder() .setProvisionDeviceKey("testProvisionKey") .setProvisionDeviceSecret("testProvisionSecret") - ).build() + ) + .setGateway(isGateway) + .build() .toByteArray(); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java index d01d7be3c1..c0643f96da 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java @@ -28,4 +28,5 @@ public class ProvisionRequest { private DeviceCredentialsType credentialsType; private ProvisionDeviceCredentialsData credentialsData; private ProvisionDeviceProfileCredentials credentials; + private Boolean gateway; } diff --git a/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java index f5c7bbb901..d1283d3ba0 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java @@ -635,6 +635,7 @@ public class JsonConverter { .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), getStrValue(jo, DataConstants.PROVISION_SECRET, true))) + .setGateway(jo.has(DataConstants.GATEWAY_PARAMETER) && jo.get(DataConstants.GATEWAY_PARAMETER).getAsBoolean()) .build(); } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index dd32ee1c67..f84c7f7c9b 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -697,6 +697,7 @@ message ProvisionDeviceRequestMsg { CredentialsType credentialsType = 2; ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 3; CredentialsDataProto credentialsDataProto = 4; + bool gateway = 5; } message ProvisionDeviceCredentialsMsg { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index afca584a80..3cd25cc390 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; @@ -27,6 +28,7 @@ import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.device.DeviceCacheEvictEvent; import org.thingsboard.server.cache.device.DeviceCacheKey; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.DeviceInfo; @@ -554,6 +556,11 @@ public class DeviceServiceImpl extends CachedVersionedEntityService { - return this.http.get(`/api/otaPackages/${otaPackageId}`, defaultHttpOptionsFromConfig(config)); + return this.http.get(`/api/otaPackage/${otaPackageId}`, defaultHttpOptionsFromConfig(config)); } public getOtaPackageInfo(otaPackageId: string, config?: RequestConfig): Observable { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts new file mode 100644 index 0000000000..d12e647254 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts @@ -0,0 +1,72 @@ +/// +/// Copyright © 2016-2024 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 { Directive, inject, Input, OnDestroy, TemplateRef } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Directive() +export abstract class GatewayConnectorBasicConfigDirective + implements ControlValueAccessor, Validator, OnDestroy { + + @Input() generalTabContent: TemplateRef; + + basicFormGroup: FormGroup; + + protected fb = inject(FormBuilder); + protected onChange!: (value: OutputBasicConfig) => void; + protected onTouched!: () => void; + protected destroy$ = new Subject(); + + constructor() { + this.basicFormGroup = this.initBasicFormGroup(); + + this.basicFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe((value) => this.onBasicFormGroupChange(value)); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + validate(): ValidationErrors | null { + return this.basicFormGroup.valid ? null : { basicFormGroup: { valid: false } }; + } + + registerOnChange(fn: (value: OutputBasicConfig) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + writeValue(config: OutputBasicConfig): void { + this.basicFormGroup.setValue(this.mapConfigToFormValue(config), { emitEvent: false }); + } + + protected onBasicFormGroupChange(value: InputBasicConfig): void { + this.onChange(this.getMappedValue(value)); + this.onTouched(); + } + + protected abstract mapConfigToFormValue(config: OutputBasicConfig): InputBasicConfig; + protected abstract getMappedValue(config: InputBasicConfig): OutputBasicConfig; + protected abstract initBasicFormGroup(): FormGroup; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts new file mode 100644 index 0000000000..944b5ffe6c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts @@ -0,0 +1,61 @@ +/// +/// Copyright © 2016-2024 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 { GatewayConnector, GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { isNumber, isString } from '@core/utils'; + +export abstract class GatewayConnectorVersionProcessor { + gatewayVersion: number; + configVersion: number; + + protected constructor(protected gatewayVersionIn: string | number, protected connector: GatewayConnector) { + this.gatewayVersion = this.parseVersion(this.gatewayVersionIn); + this.configVersion = this.parseVersion(connector.configVersion); + } + + getProcessedByVersion(): GatewayConnector { + if (this.isVersionUpdateNeeded()) { + return this.isVersionUpgradeNeeded() + ? this.getUpgradedVersion() + : this.getDowngradedVersion(); + } + + return this.connector; + } + + private isVersionUpdateNeeded(): boolean { + if (!this.gatewayVersion) { + return false; + } + + return this.configVersion !== this.gatewayVersion; + } + + private isVersionUpgradeNeeded(): boolean { + return this.gatewayVersionIn === GatewayVersion.Current && (!this.configVersion || this.configVersion < this.gatewayVersion); + } + + private parseVersion(version: string | number): number { + if (isNumber(version)) { + return version as number; + } + + return isString(version) ? parseFloat((version as string).replace(/\./g, '').slice(0, 3)) / 100 : 0; + } + + protected abstract getDowngradedVersion(): GatewayConnector; + protected abstract getUpgradedVersion(): GatewayConnector; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts new file mode 100644 index 0000000000..6ea8f4516d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts @@ -0,0 +1,68 @@ +/// +/// Copyright © 2016-2024 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 { + GatewayConnector, + ModbusBasicConfig, + ModbusBasicConfig_v3_5_2, + ModbusLegacyBasicConfig, + ModbusLegacySlave, + ModbusMasterConfig, + ModbusSlave, +} from '../gateway-widget.models'; +import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; +import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/modbus-version-mapping.util'; + +export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor { + + constructor( + protected gatewayVersionIn: string, + protected connector: GatewayConnector + ) { + super(gatewayVersionIn, connector); + } + + getUpgradedVersion(): GatewayConnector { + const configurationJson = this.connector.configurationJson; + return { + ...this.connector, + configurationJson: { + master: configurationJson.master + ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master) + : {} as ModbusMasterConfig, + slave: configurationJson.slave + ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave) + : {} as ModbusSlave, + }, + configVersion: this.gatewayVersionIn + } as GatewayConnector; + } + + getDowngradedVersion(): GatewayConnector { + const configurationJson = this.connector.configurationJson; + return { + ...this.connector, + configurationJson: { + ...configurationJson, + slave: configurationJson.slave + ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(configurationJson.slave as ModbusSlave) + : {} as ModbusLegacySlave, + master: configurationJson.master, + }, + configVersion: this.gatewayVersionIn + } as GatewayConnector; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts new file mode 100644 index 0000000000..351ad76494 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts @@ -0,0 +1,101 @@ +/// +/// Copyright © 2016-2024 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 { isEqual } from '@core/utils'; +import { + GatewayConnector, + MQTTBasicConfig, + MQTTBasicConfig_v3_5_2, + MQTTLegacyBasicConfig, + RequestMappingData, + RequestType, +} from '../gateway-widget.models'; +import { MqttVersionMappingUtil } from '../utils/mqtt-version-mapping.util'; +import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; + +export class MqttVersionProcessor extends GatewayConnectorVersionProcessor { + + private readonly mqttRequestTypeKeys = Object.values(RequestType); + + constructor( + protected gatewayVersionIn: string, + protected connector: GatewayConnector + ) { + super(gatewayVersionIn, connector); + } + + getUpgradedVersion(): GatewayConnector { + const { + connectRequests, + disconnectRequests, + attributeRequests, + attributeUpdates, + serverSideRpc + } = this.connector.configurationJson as MQTTLegacyBasicConfig; + let configurationJson = { + ...this.connector.configurationJson, + requestsMapping: MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ + connectRequests, + disconnectRequests, + attributeRequests, + attributeUpdates, + serverSideRpc + }), + mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion((this.connector.configurationJson as MQTTLegacyBasicConfig).mapping), + }; + + this.mqttRequestTypeKeys.forEach((key: RequestType) => { + const { [key]: removedValue, ...rest } = configurationJson as MQTTLegacyBasicConfig; + configurationJson = { ...rest } as any; + }); + + this.cleanUpConfigJson(configurationJson as MQTTBasicConfig_v3_5_2); + + return { + ...this.connector, + configurationJson, + configVersion: this.gatewayVersionIn + } as GatewayConnector; + } + + getDowngradedVersion(): GatewayConnector { + const { requestsMapping, mapping, ...restConfig } = this.connector.configurationJson as MQTTBasicConfig_v3_5_2; + + const updatedRequestsMapping = + MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record); + const updatedMapping = MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping); + + return { + ...this.connector, + configurationJson: { + ...restConfig, + ...updatedRequestsMapping, + mapping: updatedMapping, + }, + configVersion: this.gatewayVersionIn + } as GatewayConnector; + } + + private cleanUpConfigJson(configurationJson: MQTTBasicConfig_v3_5_2): void { + if (isEqual(configurationJson.requestsMapping, {})) { + delete configurationJson.requestsMapping; + } + + if (isEqual(configurationJson.mapping, [])) { + delete configurationJson.mapping; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts new file mode 100644 index 0000000000..e89c48fc28 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2024 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 { + GatewayConnector, LegacyServerConfig, + OPCBasicConfig, + OPCBasicConfig_v3_5_2, + OPCLegacyBasicConfig, +} from '../gateway-widget.models'; +import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; +import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/opc-version-mapping.util'; + +export class OpcVersionProcessor extends GatewayConnectorVersionProcessor { + + constructor( + protected gatewayVersionIn: string, + protected connector: GatewayConnector + ) { + super(gatewayVersionIn, connector); + } + + getUpgradedVersion(): GatewayConnector { + const server = this.connector.configurationJson.server as LegacyServerConfig; + return { + ...this.connector, + configurationJson: { + server: server ? OpcVersionMappingUtil.mapServerToUpgradedVersion(server) : {}, + mapping: server.mapping ? OpcVersionMappingUtil.mapMappingToUpgradedVersion(server.mapping) : [], + }, + configVersion: this.gatewayVersionIn + } as GatewayConnector; + } + + getDowngradedVersion(): GatewayConnector { + return { + ...this.connector, + configurationJson: { + server: OpcVersionMappingUtil.mapServerToDowngradedVersion(this.connector.configurationJson as OPCBasicConfig_v3_5_2) + }, + configVersion: this.gatewayVersionIn + } as GatewayConnector; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts index a23d8e7900..bfdd46997e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts @@ -16,7 +16,9 @@ import { ConfigurationModes, - GatewayConnector, LocalLogsConfigs, LogSavingPeriod, + GatewayConnector, + LocalLogsConfigs, + LogSavingPeriod, SecurityTypes, StorageTypes } from '@home/components/widget/lib/gateway/gateway-widget.models'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts index 569f40b185..91432b7353 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts @@ -42,8 +42,8 @@ import { import { DeviceInfoType, noLeadTrailSpacesRegex, - OPCUaSourceTypes, - SourceTypes, + OPCUaSourceType, + SourceType, SourceTypeTranslationsMap } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { coerceBoolean } from '@shared/decorators/coercion'; @@ -81,7 +81,7 @@ export class DeviceInfoTableComponent extends PageComponent implements ControlVa required = false; @Input() - sourceTypes: Array = Object.values(SourceTypes); + sourceTypes: Array = Object.values(SourceType); deviceInfoTypeValue: any; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts index 7867a7213f..de289dde2e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts @@ -42,7 +42,7 @@ import { MappingValueType, mappingValueTypesMap, noLeadTrailSpacesRegex, - OPCUaSourceTypes, + OPCUaSourceType, RpcMethodsMapping, } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -73,7 +73,7 @@ export class MappingDataKeysPanelComponent extends PageComponent implements OnIn keysType: MappingKeysType; @Input() - valueTypeKeys: Array = Object.values(MappingValueType); + valueTypeKeys: Array = Object.values(MappingValueType); @Input() valueTypeEnum = MappingValueType; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts index 05dcfa88ba..fa8dfa6466 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts @@ -40,17 +40,21 @@ import { Validator, } from '@angular/forms'; import { + AttributeUpdate, ConnectorMapping, + ConnectRequest, ConverterConnectorMapping, ConvertorTypeTranslationsMap, DeviceConnectorMapping, + DisconnectRequest, MappingInfo, MappingType, MappingTypeTranslationsMap, MappingValue, - RequestMappingData, + RequestMappingValue, RequestType, - RequestTypesTranslationsMap + RequestTypesTranslationsMap, + ServerSideRpc } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; @@ -259,16 +263,17 @@ export class MappingTableComponent implements ControlValueAccessor, Validator, A }; case MappingType.REQUESTS: let details: string; - if ((value as RequestMappingData).requestType === RequestType.ATTRIBUTE_UPDATE) { - details = (value as RequestMappingData).requestValue.attributeFilter; - } else if ((value as RequestMappingData).requestType === RequestType.SERVER_SIDE_RPC) { - details = (value as RequestMappingData).requestValue.methodFilter; + const requestValue = value as RequestMappingValue; + if (requestValue.requestType === RequestType.ATTRIBUTE_UPDATE) { + details = (requestValue.requestValue as AttributeUpdate).attributeFilter; + } else if (requestValue.requestType === RequestType.SERVER_SIDE_RPC) { + details = (requestValue.requestValue as ServerSideRpc).methodFilter; } else { - details = (value as RequestMappingData).requestValue.topicFilter; + details = (requestValue.requestValue as ConnectRequest | DisconnectRequest).topicFilter; } return { - requestType: (value as RequestMappingData).requestType, - type: this.translate.instant(RequestTypesTranslationsMap.get((value as RequestMappingData).requestType)), + requestType: (value as RequestMappingValue).requestType, + type: this.translate.instant(RequestTypesTranslationsMap.get((value as RequestMappingValue).requestType)), details }; case MappingType.OPCUA: diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts new file mode 100644 index 0000000000..9e7be382d2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts @@ -0,0 +1,81 @@ +/// +/// Copyright © 2016-2024 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 { Directive } from '@angular/core'; +import { FormControl, FormGroup, ValidationErrors } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { isEqual } from '@core/utils'; +import { GatewayConnectorBasicConfigDirective } from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; +import { + ModbusBasicConfig, + ModbusBasicConfig_v3_5_2, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +@Directive() +export abstract class ModbusBasicConfigDirective + extends GatewayConnectorBasicConfigDirective { + + enableSlaveControl: FormControl = new FormControl(false); + + constructor() { + super(); + + this.enableSlaveControl.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(enable => { + this.updateSlaveEnabling(enable); + this.basicFormGroup.get('slave').updateValueAndValidity({ emitEvent: !!this.onChange }); + }); + } + + override writeValue(basicConfig: BasicConfig & ModbusBasicConfig): void { + super.writeValue(basicConfig); + this.onEnableSlaveControl(basicConfig); + } + + override validate(): ValidationErrors | null { + const { master, slave } = this.basicFormGroup.value; + const isEmpty = !master?.slaves?.length && (isEqual(slave, {}) || !slave); + if (!this.basicFormGroup.valid || isEmpty) { + return { basicFormGroup: { valid: false } }; + } + return null; + } + + protected override initBasicFormGroup(): FormGroup { + return this.fb.group({ + master: [], + slave: [], + }); + } + + protected override onBasicFormGroupChange(value: ModbusBasicConfig_v3_5_2): void { + super.onBasicFormGroupChange(value); + this.basicFormGroup.get('slave').updateValueAndValidity({ emitEvent: !!this.onChange }); + } + + private updateSlaveEnabling(isEnabled: boolean): void { + if (isEnabled) { + this.basicFormGroup.get('slave').enable({ emitEvent: false }); + } else { + this.basicFormGroup.get('slave').disable({ emitEvent: false }); + } + } + + private onEnableSlaveControl(basicConfig: ModbusBasicConfig): void { + this.enableSlaveControl.setValue(!!basicConfig.slave && !isEqual(basicConfig.slave, {})); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts index aa0424da93..21c37ed147 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts @@ -14,28 +14,21 @@ /// limitations under the License. /// -import { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; +import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { - ControlValueAccessor, - FormBuilder, - FormControl, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormControl, - ValidationErrors, - Validator, -} from '@angular/forms'; -import { ModbusBasicConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; + ModbusBasicConfig_v3_5_2, + ModbusMasterConfig, + ModbusSlave +} from '@home/components/widget/lib/gateway/gateway-widget.models'; import { CommonModule } from '@angular/common'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; - -import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; +import { SharedModule } from '@shared/shared.module'; import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component'; import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component'; -import { isEqual } from '@core/utils'; +import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; +import { + ModbusBasicConfigDirective +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract'; @Component({ selector: 'tb-modbus-basic-config', @@ -63,80 +56,19 @@ import { isEqual } from '@core/utils'; ], styleUrls: ['./modbus-basic-config.component.scss'], }) +export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective { -export class ModbusBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - - @Input() generalTabContent: TemplateRef; - - basicFormGroup: FormGroup; - enableSlaveControl: FormControl; - - onChange: (value: ModbusBasicConfig) => void; - onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.basicFormGroup = this.fb.group({ - master: [], - slave: [], - }); - this.enableSlaveControl = new FormControl(false); - - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(({ master, slave }) => { - this.onChange({ master, slave: slave ?? {} }); - this.onTouched(); - }); - - this.enableSlaveControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(enable => { - this.updateSlaveEnabling(enable); - this.basicFormGroup.get('slave').updateValueAndValidity({emitEvent: !!this.onChange}); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: ModbusBasicConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(basicConfig: ModbusBasicConfig): void { - const editedBase = { - slave: basicConfig.slave ?? {}, - master: basicConfig.master ?? {}, + protected override mapConfigToFormValue(config: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { + return { + master: config.master ?? {} as ModbusMasterConfig, + slave: config.slave ?? {} as ModbusSlave, }; - - this.basicFormGroup.setValue(editedBase, {emitEvent: false}); - this.enableSlaveControl.setValue(!!basicConfig.slave && !isEqual(basicConfig.slave, {})); - } - - validate(basicFormControl: UntypedFormControl): ValidationErrors | null { - const { master, slave } = basicFormControl.value; - const isEmpty = !master?.slaves?.length && (isEqual(slave, {}) || !slave); - if (!this.basicFormGroup.valid || isEmpty) { - return { - basicFormGroup: {valid: false} - }; - } - return null; } - private updateSlaveEnabling(isEnabled: boolean): void { - if (isEnabled) { - this.basicFormGroup.get('slave').enable({emitEvent: false}); - } else { - this.basicFormGroup.get('slave').disable({emitEvent: false}); - } + protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { + return { + master: value.master, + slave: value.slave, + }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts new file mode 100644 index 0000000000..50f7d833fa --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts @@ -0,0 +1,76 @@ +/// +/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; +import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + ModbusBasicConfig_v3_5_2, + ModbusLegacyBasicConfig, ModbusLegacySlave, + ModbusMasterConfig, + ModbusSlave +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component'; +import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component'; +import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; +import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/modbus-version-mapping.util'; +import { + ModbusBasicConfigDirective +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract'; + +@Component({ + selector: 'tb-modbus-legacy-basic-config', + templateUrl: './modbus-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModbusLegacyBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ModbusLegacyBasicConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusSlaveConfigComponent, + ModbusMasterTableComponent, + EllipsisChipListDirective, + ], + styleUrls: ['./modbus-basic-config.component.scss'], +}) +export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective { + + protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 { + return { + master: config.master ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master) : {} as ModbusMasterConfig, + slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave, + }; + } + + protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusLegacyBasicConfig { + return { + master: value.master, + slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave) : {} as ModbusLegacySlave, + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts deleted file mode 100644 index 25f8671201..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts +++ /dev/null @@ -1,191 +0,0 @@ -/// -/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - ValidationErrors, - Validator, -} from '@angular/forms'; -import { - MappingType, - MQTTBasicConfig, - RequestMappingData, - RequestType, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { isObject } from 'lodash'; -import { - SecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; -import { - BrokerConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; -import { - MappingTableComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; -import { isDefinedAndNotNull } from '@core/utils'; - -@Component({ - selector: 'tb-mqtt-basic-config', - templateUrl: './mqtt-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => MqttBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => MqttBasicConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - WorkersConfigControlComponent, - BrokerConfigControlComponent, - MappingTableComponent, - ], - styleUrls: ['./mqtt-basic-config.component.scss'] -}) - -export class MqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - - @Input() - generalTabContent: TemplateRef; - - mappingTypes = MappingType; - basicFormGroup: FormGroup; - - private onChange: (value: MQTTBasicConfig) => void; - private onTouched: () => void; - - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.basicFormGroup = this.fb.group({ - dataMapping: [], - requestsMapping: [], - broker: [], - workers: [], - }); - - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onChange(this.getMappedMQTTConfig(value)); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: MQTTBasicConfig) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(basicConfig: MQTTBasicConfig): void { - const { broker, dataMapping = [], requestsMapping } = basicConfig; - const editedBase = { - workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { - maxNumberOfWorkers: broker.maxNumberOfWorkers, - maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, - } : {}, - dataMapping: dataMapping || [], - broker: broker || {}, - requestsMapping: Array.isArray(requestsMapping) - ? requestsMapping - : this.getRequestDataArray(requestsMapping), - }; - - this.basicFormGroup.setValue(editedBase, {emitEvent: false}); - } - - private getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig { - let { broker, workers, dataMapping, requestsMapping } = basicConfig || {}; - - if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { - broker = { - ...broker, - ...workers, - }; - } - - if ((requestsMapping as RequestMappingData[])?.length) { - requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingData[]); - } - - return { broker, workers, dataMapping, requestsMapping }; - } - - validate(): ValidationErrors | null { - return this.basicFormGroup.valid ? null : { - basicFormGroup: {valid: false} - }; - } - - private getRequestDataArray(value: Record): RequestMappingData[] { - const mappingConfigs = []; - - if (isObject(value)) { - Object.keys(value).forEach((configKey: string) => { - for (const mapping of value[configKey]) { - mappingConfigs.push({ - requestType: configKey, - requestValue: mapping - }); - } - }); - } - - return mappingConfigs; - } - - private getRequestDataObject(array: RequestMappingData[]): Record { - return array.reduce((result, { requestType, requestValue }) => { - result[requestType].push(requestValue); - return result; - }, { - connectRequests: [], - disconnectRequests: [], - attributeRequests: [], - attributeUpdates: [], - serverSideRpc: [], - }); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts new file mode 100644 index 0000000000..ce87d80a75 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts @@ -0,0 +1,82 @@ +/// +/// Copyright © 2016-2024 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 { Directive } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { + MappingType, + MQTTBasicConfig, MQTTBasicConfig_v3_5_2, + RequestMappingData, + RequestMappingValue, + RequestType +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { isObject } from '@core/utils'; +import { + GatewayConnectorBasicConfigDirective +} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; + +@Directive() +export abstract class MqttBasicConfigDirective + extends GatewayConnectorBasicConfigDirective { + + MappingType = MappingType; + + protected override initBasicFormGroup(): FormGroup { + return this.fb.group({ + mapping: [], + requestsMapping: [], + broker: [], + workers: [], + }); + } + + protected getRequestDataArray(value: Record): RequestMappingData[] { + const mappingConfigs = []; + + if (isObject(value)) { + Object.keys(value).forEach((configKey: string) => { + for (const mapping of value[configKey]) { + mappingConfigs.push({ + requestType: configKey, + requestValue: mapping + }); + } + }); + } + + return mappingConfigs; + } + + protected getRequestDataObject(array: RequestMappingValue[]): Record { + return array.reduce((result, { requestType, requestValue }) => { + result[requestType].push(requestValue); + return result; + }, { + connectRequests: [], + disconnectRequests: [], + attributeRequests: [], + attributeUpdates: [], + serverSideRpc: [], + }); + } + + writeValue(basicConfig: BasicConfig): void { + this.basicFormGroup.setValue(this.mapConfigToFormValue(basicConfig), { emitEvent: false }); + } + + protected abstract override mapConfigToFormValue(config: BasicConfig): MQTTBasicConfig_v3_5_2; + protected abstract override getMappedValue(config: MQTTBasicConfig): BasicConfig; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html similarity index 90% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html index 9849923218..b602ba20d4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html @@ -24,12 +24,12 @@
- +
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts new file mode 100644 index 0000000000..9af627352a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts @@ -0,0 +1,103 @@ +/// +/// Copyright © 2016-2024 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 { Component, forwardRef, ChangeDetectionStrategy } from '@angular/core'; +import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; +import { + BrokerConfig, + MQTTBasicConfig_v3_5_2, + RequestMappingData, + RequestMappingValue, + RequestType, WorkersConfig +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { + MqttBasicConfigDirective +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; +import { isDefinedAndNotNull } from '@core/utils'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { + SecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; +import { + WorkersConfigControlComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; +import { + BrokerConfigControlComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; +import { + MappingTableComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; + +@Component({ + selector: 'tb-mqtt-basic-config', + templateUrl: './mqtt-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MqttBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => MqttBasicConfigComponent), + multi: true + } + ], + styleUrls: ['./mqtt-basic-config.component.scss'], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + WorkersConfigControlComponent, + BrokerConfigControlComponent, + MappingTableComponent, + ], +}) +export class MqttBasicConfigComponent extends MqttBasicConfigDirective { + + protected override mapConfigToFormValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { + const { broker, mapping = [], requestsMapping } = basicConfig; + return{ + workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { + maxNumberOfWorkers: broker.maxNumberOfWorkers, + maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, + } : {} as WorkersConfig, + mapping: mapping ?? [], + broker: broker ?? {} as BrokerConfig, + requestsMapping: this.getRequestDataArray(requestsMapping as Record), + }; + } + + protected override getMappedValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { + let { broker, workers, mapping, requestsMapping } = basicConfig || {}; + + if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { + broker = { + ...broker, + ...workers, + }; + } + + if ((requestsMapping as RequestMappingData[])?.length) { + requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingValue[]); + } + + return { broker, mapping, requestsMapping }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts new file mode 100644 index 0000000000..e4577c653e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts @@ -0,0 +1,124 @@ +/// +/// Copyright © 2016-2024 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 { Component, forwardRef, ChangeDetectionStrategy } from '@angular/core'; +import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; +import { + BrokerConfig, + MQTTBasicConfig_v3_5_2, + MQTTLegacyBasicConfig, + RequestMappingData, + RequestMappingValue, + RequestType, WorkersConfig +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util'; +import { + MqttBasicConfigDirective +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; +import { isDefinedAndNotNull } from '@core/utils'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { + SecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; +import { + WorkersConfigControlComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; +import { + BrokerConfigControlComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; +import { + MappingTableComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; + +@Component({ + selector: 'tb-mqtt-legacy-basic-config', + templateUrl: './mqtt-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), + multi: true + } + ], + styleUrls: ['./mqtt-basic-config.component.scss'], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + WorkersConfigControlComponent, + BrokerConfigControlComponent, + MappingTableComponent, + ], +}) +export class MqttLegacyBasicConfigComponent extends MqttBasicConfigDirective { + + protected override mapConfigToFormValue(config: MQTTLegacyBasicConfig): MQTTBasicConfig_v3_5_2 { + const { + broker, + mapping = [], + connectRequests = [], + disconnectRequests = [], + attributeRequests = [], + attributeUpdates = [], + serverSideRpc = [] + } = config as MQTTLegacyBasicConfig; + const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ + connectRequests, + disconnectRequests, + attributeRequests, + attributeUpdates, + serverSideRpc + }); + return { + workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { + maxNumberOfWorkers: broker.maxNumberOfWorkers, + maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, + } : {} as WorkersConfig, + mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion(mapping) || [], + broker: broker || {} as BrokerConfig, + requestsMapping: this.getRequestDataArray(updatedRequestMapping), + }; + } + + protected override getMappedValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTLegacyBasicConfig { + let { broker, workers, mapping, requestsMapping } = basicConfig || {}; + + if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { + broker = { + ...broker, + ...workers, + }; + } + + if ((requestsMapping as RequestMappingData[])?.length) { + requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingValue[]); + } + + return { + broker, + mapping: MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping), + ...(MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record)) + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts similarity index 97% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts index e44506dccc..61c62a49e1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts @@ -36,7 +36,7 @@ import { CommonModule } from '@angular/common'; import { generateSecret } from '@core/utils'; import { Subject } from 'rxjs'; import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; -import { SecurityConfigComponent } from '../security-config/security-config.component'; +import { SecurityConfigComponent } from '../../security-config/security-config.component'; @Component({ selector: 'tb-broker-config-control', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts deleted file mode 100644 index 39198c9696..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts +++ /dev/null @@ -1,134 +0,0 @@ -/// -/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; -import { - ControlValueAccessor, - FormBuilder, - FormGroup, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - ValidationErrors, - Validator, -} from '@angular/forms'; -import { - ConnectorType, - MappingType, - OPCBasicConfig, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { SharedModule } from '@shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; -import { - SecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; -import { - WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; -import { - BrokerConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; -import { - MappingTableComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; -import { - OpcServerConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component'; - -@Component({ - selector: 'tb-opc-ua-basic-config', - templateUrl: './opc-ua-basic-config.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => OpcUaBasicConfigComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => OpcUaBasicConfigComponent), - multi: true - } - ], - standalone: true, - imports: [ - CommonModule, - SharedModule, - SecurityConfigComponent, - WorkersConfigControlComponent, - BrokerConfigControlComponent, - MappingTableComponent, - OpcServerConfigComponent, - ], - styleUrls: ['./opc-ua-basic-config.component.scss'] -}) - -export class OpcUaBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { - @Input() generalTabContent: TemplateRef; - - mappingTypes = MappingType; - basicFormGroup: FormGroup; - - onChange!: (value: string) => void; - onTouched!: () => void; - - protected readonly connectorType = ConnectorType; - private destroy$ = new Subject(); - - constructor(private fb: FormBuilder) { - this.basicFormGroup = this.fb.group({ - mapping: [], - server: [], - }); - - this.basicFormGroup.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => { - this.onChange(value); - this.onTouched(); - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - registerOnChange(fn: (value: string) => void): void { - this.onChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.onTouched = fn; - } - - writeValue(basicConfig: OPCBasicConfig): void { - const editedBase = { - server: basicConfig.server || {}, - mapping: basicConfig.mapping || [], - }; - - this.basicFormGroup.setValue(editedBase, {emitEvent: false}); - } - - validate(): ValidationErrors | null { - return this.basicFormGroup.valid ? null : { - basicFormGroup: {valid: false} - }; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html similarity index 98% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html index 584679fa2c..523705d029 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html @@ -84,7 +84,7 @@ -
+
{{ 'gateway.poll-period' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts similarity index 90% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts index c838121b76..c000139ded 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectionStrategy, Component, forwardRef, OnDestroy } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -39,6 +39,7 @@ import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; import { HOUR } from '@shared/models/time/time.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-opc-server-config', @@ -64,7 +65,11 @@ import { HOUR } from '@shared/models/time/time.models'; SecurityConfigComponent, ] }) -export class OpcServerConfigComponent implements ControlValueAccessor, Validator, OnDestroy { +export class OpcServerConfigComponent implements ControlValueAccessor, Validator, AfterViewInit, OnDestroy { + + @Input() + @coerceBoolean() + hideNewFields: boolean = false; securityPolicyTypes = SecurityPolicyTypes; serverConfigFormGroup: UntypedFormGroup; @@ -95,6 +100,12 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator }); } + ngAfterViewInit(): void { + if (this.hideNewFields) { + this.serverConfigFormGroup.get('pollPeriodInMillis').disable({emitEvent: false}); + } + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html similarity index 92% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html index 5f8b25af9d..2296a472a4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html @@ -20,7 +20,7 @@ - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts new file mode 100644 index 0000000000..c45eafcc67 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts @@ -0,0 +1,88 @@ +/// +/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; +import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + MappingType, + OPCBasicConfig_v3_5_2, + ServerConfig +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; +import { + SecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; +import { + OpcServerConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; +import { + GatewayConnectorBasicConfigDirective +} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; + +@Component({ + selector: 'tb-opc-ua-basic-config', + templateUrl: './opc-ua-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => OpcUaBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => OpcUaBasicConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + MappingTableComponent, + OpcServerConfigComponent, + ], + styleUrls: ['./opc-ua-basic-config.component.scss'] +}) +export class OpcUaBasicConfigComponent extends GatewayConnectorBasicConfigDirective { + + mappingTypes = MappingType; + isLegacy = false; + + protected override initBasicFormGroup(): FormGroup { + return this.fb.group({ + mapping: [], + server: [], + }); + } + + protected override mapConfigToFormValue(config: OPCBasicConfig_v3_5_2): OPCBasicConfig_v3_5_2 { + return { + server: config.server ?? {} as ServerConfig, + mapping: config.mapping ?? [], + }; + } + + protected override getMappedValue(value: OPCBasicConfig_v3_5_2): OPCBasicConfig_v3_5_2 { + return { + server: value.server, + mapping: value.mapping, + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts new file mode 100644 index 0000000000..9458858cbf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts @@ -0,0 +1,88 @@ +/// +/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; +import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + MappingType, + OPCBasicConfig_v3_5_2, + OPCLegacyBasicConfig, ServerConfig, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; +import { + SecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; +import { + OpcServerConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; +import { + GatewayConnectorBasicConfigDirective +} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; +import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/opc-version-mapping.util'; + +@Component({ + selector: 'tb-opc-ua-legacy-basic-config', + templateUrl: './opc-ua-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => OpcUaLegacyBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => OpcUaLegacyBasicConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + MappingTableComponent, + OpcServerConfigComponent, + ], + styleUrls: ['./opc-ua-basic-config.component.scss'] +}) +export class OpcUaLegacyBasicConfigComponent extends GatewayConnectorBasicConfigDirective { + + mappingTypes = MappingType; + isLegacy = true; + + protected override initBasicFormGroup(): FormGroup { + return this.fb.group({ + mapping: [], + server: [], + }); + } + + protected override mapConfigToFormValue(config: OPCLegacyBasicConfig): OPCBasicConfig_v3_5_2 { + return { + server: config.server ? OpcVersionMappingUtil.mapServerToUpgradedVersion(config.server) : {} as ServerConfig, + mapping: config.server?.mapping ? OpcVersionMappingUtil.mapMappingToUpgradedVersion(config.server.mapping) : [], + }; + } + + protected override getMappedValue(value: OPCBasicConfig_v3_5_2): OPCLegacyBasicConfig { + return { + server: OpcVersionMappingUtil.mapServerToDowngradedVersion(value), + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts index eca1497d22..c8235cb697 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts @@ -26,12 +26,14 @@ import { AddConnectorConfigData, ConnectorType, CreatedConnectorConfigData, + GatewayConnector, GatewayConnectorDefaultTypesTranslatesMap, GatewayLogLevel, - getDefaultConfig, + GatewayVersion, + GatewayVersionedDefaultConfig, noLeadTrailSpacesRegex } from '@home/components/widget/lib/gateway/gateway-widget.models'; -import { Subject } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import { ResourcesService } from '@core/services/resources.service'; import { takeUntil, tap } from 'rxjs/operators'; import { helpBaseUrl } from '@shared/models/constants'; @@ -42,7 +44,8 @@ import { helpBaseUrl } from '@shared/models/constants'; styleUrls: ['./add-connector-dialog.component.scss'], providers: [], }) -export class AddConnectorDialogComponent extends DialogComponent> implements OnInit, OnDestroy { +export class AddConnectorDialogComponent + extends DialogComponent> implements OnInit, OnDestroy { connectorForm: UntypedFormGroup; @@ -95,8 +98,15 @@ export class AddConnectorDialogComponent extends DialogComponent { - value.configurationJson = defaultConfig; + this.getDefaultConfig(value.type).subscribe((defaultConfig: GatewayVersionedDefaultConfig) => { + const gatewayVersion = this.data.gatewayVersion; + if (gatewayVersion) { + value.configVersion = gatewayVersion; + } + value.configurationJson = (gatewayVersion === GatewayVersion.Current + ? defaultConfig[this.data.gatewayVersion] + : defaultConfig.legacy) + ?? defaultConfig; if (this.connectorForm.valid) { this.dialogRef.close(value); } @@ -130,4 +140,8 @@ export class AddConnectorDialogComponent extends DialogComponent { + return this.resourcesService.loadJsonResource(`/assets/metadata/connector-default-configs/${type}.json`); + }; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts index 9e5a43bc17..ef8d7ed7ce 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts @@ -43,15 +43,16 @@ import { MappingType, MappingTypeTranslationsMap, noLeadTrailSpacesRegex, - OPCUaSourceTypes, + OPCUaSourceType, QualityTypes, QualityTypeTranslationsMap, + RequestMappingData, RequestMappingFormValue, RequestType, RequestTypesTranslationsMap, RpcMethod, ServerSideRPCType, - SourceTypes, + SourceType, SourceTypeTranslationsMap, Timeseries } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -82,10 +83,10 @@ export class MappingDialogComponent extends DialogComponent; - OPCUaSourceTypesEnum = OPCUaSourceTypes; - sourceTypesEnum = SourceTypes; + sourceTypes: SourceType[] = Object.values(SourceType); + OPCUaSourceTypes = Object.values(OPCUaSourceType) as Array; + OPCUaSourceTypesEnum = OPCUaSourceType; + sourceTypesEnum = SourceType; SourceTypeTranslationsMap = SourceTypeTranslationsMap; requestTypes: RequestType[] = Object.values(RequestType); @@ -230,8 +231,8 @@ export class MappingDialogComponent extends DialogComponent + }; default: return this.data.value as DeviceConnectorMapping; } @@ -349,10 +350,10 @@ export class MappingDialogComponent extends DialogComponent {{ initialConnector?.type ? GatewayConnectorTypesTranslatesMap.get(initialConnector.type) : '' }} {{ 'gateway.configuration' | translate }} + v{{connectorForm.get('configVersion').value}}
@@ -175,18 +176,36 @@
- - - - - - + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss index f151887c92..5d8c6aad5b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss @@ -22,6 +22,11 @@ overflow-x: auto; padding: 0; + .version-placeholder { + color: gray; + font-size: 12px + } + .connector-container { height: 100%; width: 100%; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index c4cbf9d698..03765613da 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -59,12 +59,16 @@ import { GatewayConnectorDefaultTypesTranslatesMap, GatewayLogLevel, noLeadTrailSpacesRegex, + GatewayVersion, } from './gateway-widget.models'; import { MatDialog } from '@angular/material/dialog'; import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component'; import { debounceTime, filter, switchMap, take, takeUntil, tap } from 'rxjs/operators'; import { ErrorStateMatcher } from '@angular/material/core'; import { PageData } from '@shared/models/page/page-data'; +import { + GatewayConnectorVersionMappingUtil +} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util'; export class ForceErrorStateMatcher implements ErrorStateMatcher { isErrorState(control: FormControl | null): boolean { @@ -98,6 +102,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie readonly displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions']; readonly GatewayConnectorTypesTranslatesMap = GatewayConnectorDefaultTypesTranslatesMap; readonly ConnectorConfigurationModes = ConfigurationModes; + readonly GatewayVersion = GatewayVersion; pageLink: PageLink; dataSource: MatTableDataSource; @@ -106,6 +111,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie mode: ConfigurationModes = this.ConnectorConfigurationModes.BASIC; initialConnector: GatewayConnector; + private gatewayVersion: string; private inactiveConnectors: Array; private attributeDataSource: AttributeDatasource; private inactiveConnectorsDataSource: AttributeDatasource; @@ -239,6 +245,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie delete value.class; } + if (this.gatewayVersion && !value.configVersion) { + value.configVersion = this.gatewayVersion; + } + value.ts = Date.now(); return value; @@ -263,7 +273,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie }); } - isConnectorSynced(attribute: GatewayAttributeData) { + isConnectorSynced(attribute: GatewayAttributeData): boolean { const connectorData = attribute.value; if (!connectorData.ts || attribute.skipSync) { return false; @@ -340,7 +350,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie class: '', configuration: '', configurationJson: {}, - basicConfig: {} + basicConfig: {}, + configVersion: '' }, {emitEvent: false}); this.connectorForm.markAsPristine(); } @@ -483,7 +494,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie return this.dialog.open(AddConnectorDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { dataSourceData: this.dataSource.data } + data: { + dataSourceData: this.dataSource.data, + gatewayVersion: this.gatewayVersion, + } }).afterClosed(); } @@ -526,7 +540,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie class: [''], configuration: [''], configurationJson: [{}, [Validators.required]], - basicConfig: [{}] + basicConfig: [{}], + configVersion: [''], }); this.connectorForm.disable(); } @@ -560,10 +575,12 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie forkJoin([ this.attributeService.getEntityAttributes(this.device, AttributeScope.SHARED_SCOPE, ['active_connectors']), - this.attributeService.getEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, ['inactive_connectors']) + this.attributeService.getEntityAttributes(this.device, AttributeScope.SERVER_SCOPE, ['inactive_connectors']), + this.attributeService.getEntityAttributes(this.device, AttributeScope.CLIENT_SCOPE, ['Version']) ]).pipe(takeUntil(this.destroy$)).subscribe(attributes => { this.activeConnectors = this.parseConnectors(attributes[0]); this.inactiveConnectors = this.parseConnectors(attributes[1]); + this.gatewayVersion = attributes[2][0]?.value; this.updateData(true); }); @@ -721,12 +738,12 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.connectorForm.enable(); } - const connectorState = { + const connectorState = GatewayConnectorVersionMappingUtil.getConfig({ configuration: '', key: 'auto', configurationJson: {} as ConnectorBaseConfig, ...connector, - }; + }, this.gatewayVersion); connectorState.basicConfig = connectorState.configurationJson; this.initialConnector = connectorState; @@ -739,6 +756,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie case ConnectorType.OPCUA: case ConnectorType.MODBUS: this.connectorForm.get('mode').setValue(connector.mode || ConfigurationModes.BASIC, {emitEvent: false}); + this.connectorForm.get('configVersion').setValue(connector.configVersion, {emitEvent: false}); setTimeout(() => { this.connectorForm.patchValue(connector, {emitEvent: false}); this.connectorForm.markAsPristine(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 74ea647b14..0f940e28ff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -14,8 +14,6 @@ /// limitations under the License. /// -import { ResourcesService } from '@core/services/resources.service'; -import { Observable } from 'rxjs'; import { helpBaseUrl, ValueTypeData } from '@shared/models/constants'; import { AttributeData } from '@shared/models/telemetry/telemetry.models'; @@ -115,21 +113,30 @@ export interface GatewayAttributeData extends AttributeData { skipSync?: boolean; } -export interface GatewayConnector { +export interface GatewayConnectorBase { name: string; type: ConnectorType; configuration?: string; - configurationJson: ConnectorBaseConfig; - basicConfig?: ConnectorBaseConfig; logLevel: string; key?: string; class?: string; mode?: ConfigurationModes; + configVersion?: string; +} + +export interface GatewayConnector extends GatewayConnectorBase { + configurationJson: BaseConfig; + basicConfig?: BaseConfig; +} + +export interface GatewayVersionedDefaultConfig { + legacy: GatewayConnector; + '3.5.2': GatewayConnector; } export interface DataMapping { topicFilter: string; - QoS: string; + QoS: string | number; converter: Converter; } @@ -181,11 +188,20 @@ export interface ConnectorSecurity { mode?: ModeType; } -export type ConnectorMapping = DeviceConnectorMapping | RequestMappingData | ConverterConnectorMapping; +export enum GatewayVersion { + Current = '3.5.2', + Legacy = 'legacy' +} + +export type ConnectorMapping = DeviceConnectorMapping | RequestMappingValue | ConverterConnectorMapping; export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingFormValue | ConverterMappingFormValue; -export type ConnectorBaseConfig = ConnectorBaseInfo | MQTTBasicConfig | OPCBasicConfig | ModbusBasicConfig; +export type ConnectorBaseConfig = ConnectorBaseConfig_v3_5_2 | ConnectorLegacyConfig; + +export type ConnectorLegacyConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig | OPCLegacyBasicConfig | ModbusBasicConfig; + +export type ConnectorBaseConfig_v3_5_2 = ConnectorBaseInfo | MQTTBasicConfig_v3_5_2 | OPCBasicConfig_v3_5_2; export interface ConnectorBaseInfo { name: string; @@ -194,33 +210,64 @@ export interface ConnectorBaseInfo { logLevel: GatewayLogLevel; } -export interface MQTTBasicConfig { - dataMapping: ConverterConnectorMapping[]; - requestsMapping: Record | RequestMappingData[]; +export type MQTTBasicConfig = MQTTBasicConfig_v3_5_2 | MQTTLegacyBasicConfig; + +export interface MQTTBasicConfig_v3_5_2 { + mapping: ConverterConnectorMapping[]; + requestsMapping: Record | RequestMappingData[] | RequestMappingValue[]; broker: BrokerConfig; workers?: WorkersConfig; } -export interface OPCBasicConfig { +export interface MQTTLegacyBasicConfig { + mapping: LegacyConverterConnectorMapping[]; + broker: BrokerConfig; + workers?: WorkersConfig; + connectRequests: LegacyRequestMappingData[]; + disconnectRequests: LegacyRequestMappingData[]; + attributeRequests: LegacyRequestMappingData[]; + attributeUpdates: LegacyRequestMappingData[]; + serverSideRpc: LegacyRequestMappingData[]; +} + +export type OPCBasicConfig = OPCBasicConfig_v3_5_2 | OPCLegacyBasicConfig; + +export interface OPCBasicConfig_v3_5_2 { mapping: DeviceConnectorMapping[]; server: ServerConfig; } -export interface ModbusBasicConfig { +export interface OPCLegacyBasicConfig { + server: LegacyServerConfig; +} + +export interface LegacyServerConfig extends Omit { + mapping: LegacyDeviceConnectorMapping[]; + disableSubscriptions: boolean; +} + +export type ModbusBasicConfig = ModbusBasicConfig_v3_5_2 | ModbusLegacyBasicConfig; + +export interface ModbusBasicConfig_v3_5_2 { master: ModbusMasterConfig; slave: ModbusSlave; } +export interface ModbusLegacyBasicConfig { + master: ModbusMasterConfig; + slave: ModbusLegacySlave; +} + export interface WorkersConfig { maxNumberOfWorkers: number; maxMessageNumberPerWorker: number; } -interface DeviceInfo { +export interface ConnectorDeviceInfo { deviceNameExpression: string; - deviceNameExpressionSource: string; + deviceNameExpressionSource: SourceType | OPCUaSourceType; deviceProfileExpression: string; - deviceProfileExpressionSource: string; + deviceProfileExpressionSource: SourceType | OPCUaSourceType; } export interface Attribute { @@ -229,13 +276,23 @@ export interface Attribute { value: string; } +export interface LegacyAttribute { + key: string; + path: string; +} + export interface Timeseries { key: string; type: string; value: string; } -interface RpcArgument { +export interface LegacyTimeseries { + key: string; + path: string; +} + +export interface RpcArgument { type: string; value: number; } @@ -245,28 +302,59 @@ export interface RpcMethod { arguments: RpcArgument[]; } +export interface LegacyRpcMethod { + method: string; + arguments: unknown[]; +} + export interface AttributesUpdate { key: string; type: string; value: string; } +export interface LegacyDeviceAttributeUpdate { + attributeOnThingsBoard: string; + attributeOnDevice: string; +} + export interface Converter { type: ConvertorType; - deviceNameJsonExpression: string; - deviceTypeJsonExpression: string; + deviceInfo?: ConnectorDeviceInfo; sendDataOnlyOnChange: boolean; timeout: number; - attributes: Attribute[]; - timeseries: Timeseries[]; + attributes?: Attribute[]; + timeseries?: Timeseries[]; + extension?: string; + cached?: boolean; + extensionConfig?: Record; +} + +export interface LegacyConverter extends Converter { + deviceNameJsonExpression?: string; + deviceTypeJsonExpression?: string; + deviceNameTopicExpression?: string; + deviceTypeTopicExpression?: string; + deviceNameExpression?: string; + deviceNameExpressionSource?: string; + deviceTypeExpression?: string; + deviceProfileExpression?: string; + deviceProfileExpressionSource?: string; + ['extension-config']?: Record; } export interface ConverterConnectorMapping { topicFilter: string; - subscriptionQos?: string; + subscriptionQos?: string | number; converter: Converter; } +export interface LegacyConverterConnectorMapping { + topicFilter: string; + subscriptionQos?: string | number; + converter: LegacyConverter; +} + export type ConverterMappingFormValue = Omit & { converter: { type: ConvertorType; @@ -275,14 +363,24 @@ export type ConverterMappingFormValue = Omit; + gatewayVersion: string; } export interface CreatedConnectorConfigData { @@ -591,13 +690,13 @@ export const ConvertorTypeTranslationsMap = new Map( ] ); -export enum SourceTypes { +export enum SourceType { MSG = 'message', TOPIC = 'topic', CONST = 'constant' } -export enum OPCUaSourceTypes { +export enum OPCUaSourceType { PATH = 'path', IDENTIFIER = 'identifier', CONST = 'constant' @@ -608,33 +707,116 @@ export enum DeviceInfoType { PARTIAL = 'partial' } -export const SourceTypeTranslationsMap = new Map( +export const SourceTypeTranslationsMap = new Map( [ - [SourceTypes.MSG, 'gateway.source-type.msg'], - [SourceTypes.TOPIC, 'gateway.source-type.topic'], - [SourceTypes.CONST, 'gateway.source-type.const'], - [OPCUaSourceTypes.PATH, 'gateway.source-type.path'], - [OPCUaSourceTypes.IDENTIFIER, 'gateway.source-type.identifier'], - [OPCUaSourceTypes.CONST, 'gateway.source-type.const'] + [SourceType.MSG, 'gateway.source-type.msg'], + [SourceType.TOPIC, 'gateway.source-type.topic'], + [SourceType.CONST, 'gateway.source-type.const'], + [OPCUaSourceType.PATH, 'gateway.source-type.path'], + [OPCUaSourceType.IDENTIFIER, 'gateway.source-type.identifier'], + [OPCUaSourceType.CONST, 'gateway.source-type.const'] ] ); -export interface RequestMappingData { +export interface RequestMappingValue { requestType: RequestType; - requestValue: RequestDataItem; + requestValue: RequestMappingData; } -export type RequestMappingFormValue = Omit & { - requestValue: Record; -}; - -export interface RequestDataItem { - type: string; - details: string; +export interface RequestMappingFormValue { requestType: RequestType; - methodFilter?: string; - attributeFilter?: string; - topicFilter?: string; + requestValue: Record; +} + +export type RequestMappingData = ConnectRequest | DisconnectRequest | AttributeRequest | AttributeUpdate | ServerSideRpc; + +export type LegacyRequestMappingData = + LegacyConnectRequest + | LegacyDisconnectRequest + | LegacyAttributeRequest + | LegacyAttributeUpdate + | LegacyServerSideRpc; + +export interface ConnectRequest { + topicFilter: string; + deviceInfo: ConnectorDeviceInfo; +} + +export interface DisconnectRequest { + topicFilter: string; + deviceInfo: ConnectorDeviceInfo; +} + +export interface AttributeRequest { + retain: boolean; + topicFilter: string; + deviceInfo: ConnectorDeviceInfo; + attributeNameExpressionSource: SourceType; + attributeNameExpression: string; + topicExpression: string; + valueExpression: string; +} + +export interface AttributeUpdate { + retain: boolean; + deviceNameFilter: string; + attributeFilter: string; + topicExpression: string; + valueExpression: string; +} + +export interface ServerSideRpc { + type: ServerSideRpcType; + deviceNameFilter: string; + methodFilter: string; + requestTopicExpression: string; + responseTopicExpression?: string; + responseTopicQoS?: number; + responseTimeout?: number; + valueExpression: string; +} + +export enum ServerSideRpcType { + WithResponse = 'twoWay', + WithoutResponse = 'oneWay' +} + +export interface LegacyConnectRequest { + topicFilter: string; + deviceNameJsonExpression?: string; + deviceNameTopicExpression?: string; +} + +interface LegacyDisconnectRequest { + topicFilter: string; + deviceNameJsonExpression?: string; + deviceNameTopicExpression?: string; +} + +interface LegacyAttributeRequest { + retain: boolean; + topicFilter: string; + deviceNameJsonExpression: string; + attributeNameJsonExpression: string; + topicExpression: string; + valueExpression: string; +} + +interface LegacyAttributeUpdate { + retain: boolean; + deviceNameFilter: string; + attributeFilter: string; + topicExpression: string; + valueExpression: string; +} + +interface LegacyServerSideRpc { + deviceNameFilter: string; + methodFilter: string; + requestTopicExpression: string; + responseTopicExpression?: string; + responseTimeout?: number; + valueExpression: string; } export enum RequestType { @@ -708,9 +890,6 @@ export enum ServerSideRPCType { TWO_WAY = 'twoWay' } -export const getDefaultConfig = (resourcesService: ResourcesService, type: string): Observable => - resourcesService.loadJsonResource(`/assets/metadata/connector-default-configs/${type}.json`); - export enum MappingValueType { STRING = 'string', INTEGER = 'integer', @@ -938,7 +1117,7 @@ export interface SlaveConfig { pollPeriod: number; unitId: number; deviceName: string; - deviceType: string; + deviceType?: string; sendDataOnlyOnChange: boolean; connectAttemptTimeMs: number; connectAttemptCount: number; @@ -990,8 +1169,19 @@ export interface ModbusSlave { security: ModbusSecurity; } +export interface ModbusLegacySlave extends Omit { + values: ModbusLegacyRegisterValues; +} + export type ModbusValuesState = ModbusRegisterValues | ModbusValues; +export interface ModbusLegacyRegisterValues { + holding_registers: ModbusValues[]; + coils_initializer: ModbusValues[]; + input_registers: ModbusValues[]; + discrete_inputs: ModbusValues[]; +} + export interface ModbusRegisterValues { holding_registers: ModbusValues; coils_initializer: ModbusValues; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts index 0fec377860..06526ce08e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts @@ -17,8 +17,8 @@ import { Pipe, PipeTransform } from '@angular/core'; import { MappingValueType, - OPCUaSourceTypes, - SourceTypes + OPCUaSourceType, + SourceType } from '@home/components/widget/lib/gateway/gateway-widget.models'; @Pipe({ @@ -26,9 +26,9 @@ import { standalone: true, }) export class GatewayHelpLinkPipe implements PipeTransform { - transform(field: string, sourceType: SourceTypes | OPCUaSourceTypes, sourceTypes?: Array ): string { - if (!sourceTypes || sourceTypes?.includes(OPCUaSourceTypes.PATH)) { - if (sourceType !== OPCUaSourceTypes.CONST) { + transform(field: string, sourceType: SourceType | OPCUaSourceType, sourceTypes?: Array ): string { + if (!sourceTypes || sourceTypes?.includes(OPCUaSourceType.PATH)) { + if (sourceType !== OPCUaSourceType.CONST) { return `widget/lib/gateway/${field}-${sourceType}_fn`; } else { return; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts new file mode 100644 index 0000000000..90c89ea316 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2024 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 { + ConnectorType, + GatewayConnector, + ModbusBasicConfig, + MQTTBasicConfig, + OPCBasicConfig, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { MqttVersionProcessor } from '@home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract'; +import { OpcVersionProcessor } from '@home/components/widget/lib/gateway/abstract/opc-version-processor.abstract'; +import { ModbusVersionProcessor } from '@home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract'; + +export abstract class GatewayConnectorVersionMappingUtil { + + static getConfig(connector: GatewayConnector, gatewayVersion: string): GatewayConnector { + switch(connector.type) { + case ConnectorType.MQTT: + return new MqttVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); + case ConnectorType.OPCUA: + return new OpcVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); + case ConnectorType.MODBUS: + return new ModbusVersionProcessor(gatewayVersion, connector as GatewayConnector).getProcessedByVersion(); + default: + return connector; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts new file mode 100644 index 0000000000..fbb78059e1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts @@ -0,0 +1,80 @@ +/// +/// Copyright © 2016-2024 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 { + ModbusDataType, + ModbusLegacyRegisterValues, + ModbusLegacySlave, + ModbusMasterConfig, + ModbusRegisterValues, + ModbusSlave, + ModbusValue, + ModbusValues, + SlaveConfig +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +export class ModbusVersionMappingUtil { + + static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { + return { + slaves: master.slaves.map((slave: SlaveConfig) => ({ + ...slave, + deviceType: slave.deviceType ?? 'default', + })) + }; + } + + static mapSlaveToDowngradedVersion(slave: ModbusSlave): ModbusLegacySlave { + const values = Object.keys(slave.values).reduce((acc, valueKey) => { + acc = { + ...acc, + [valueKey]: [ + slave.values[valueKey] + ] + }; + return acc; + }, {} as ModbusLegacyRegisterValues); + return { + ...slave, + values + }; + } + + static mapSlaveToUpgradedVersion(slave: ModbusLegacySlave): ModbusSlave { + const values = Object.keys(slave.values).reduce((acc, valueKey) => { + acc = { + ...acc, + [valueKey]: this.mapValuesToUpgradedVersion(slave.values[valueKey][0]) + }; + return acc; + }, {} as ModbusRegisterValues); + return { + ...slave, + values + }; + } + + private static mapValuesToUpgradedVersion(registerValues: ModbusValues): ModbusValues { + return Object.keys(registerValues).reduce((acc, valueKey) => { + acc = { + ...acc, + [valueKey]: registerValues[valueKey].map((value: ModbusValue) => + ({ ...value, type: (value.type as string) === 'int' ? ModbusDataType.INT16 : value.type })) + }; + return acc; + }, {} as ModbusValues); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts new file mode 100644 index 0000000000..23cc03dc4e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts @@ -0,0 +1,217 @@ +/// +/// Copyright © 2016-2024 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 { deleteNullProperties } from '@core/utils'; +import { + AttributeRequest, + ConnectorDeviceInfo, + Converter, + ConverterConnectorMapping, + ConvertorType, + LegacyConverter, + LegacyConverterConnectorMapping, + LegacyRequestMappingData, + RequestMappingData, + RequestType, + ServerSideRpc, + ServerSideRpcType, + SourceType +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +export class MqttVersionMappingUtil { + + static readonly mqttRequestTypeKeys = Object.values(RequestType); + static readonly mqttRequestMappingOldFields = + ['attributeNameJsonExpression', 'deviceNameJsonExpression', 'deviceNameTopicExpression', 'extension-config']; + static readonly mqttRequestMappingNewFields = + ['attributeNameExpressionSource', 'responseTopicQoS', 'extensionConfig']; + + static mapMappingToUpgradedVersion( + mapping: LegacyConverterConnectorMapping[] + ): ConverterConnectorMapping[] { + return mapping?.map(({ converter, topicFilter, subscriptionQos = 1 }) => { + const deviceInfo = converter.deviceInfo ?? this.extractConverterDeviceInfo(converter); + + const newConverter = { + ...converter, + deviceInfo, + extensionConfig: converter.extensionConfig || converter['extension-config'] || null + }; + + this.cleanUpOldFields(newConverter); + + return { converter: newConverter, topicFilter, subscriptionQos }; + }) as ConverterConnectorMapping[]; + } + + static mapRequestsToUpgradedVersion( + requestMapping: Record + ): Record { + return this.mqttRequestTypeKeys.reduce((acc, key: RequestType) => { + if (!requestMapping[key]) { + return acc; + } + + acc[key] = requestMapping[key].map(value => { + const newValue = this.mapRequestToUpgradedVersion(value as LegacyRequestMappingData, key); + + this.cleanUpOldFields(newValue as {}); + + return newValue; + }); + + return acc; + }, {}) as Record; + } + + static mapRequestsToDowngradedVersion( + requestsMapping: Record + ): Record { + return this.mqttRequestTypeKeys.reduce((acc, key) => { + if (!requestsMapping[key]) { + return acc; + } + + acc[key] = requestsMapping[key].map((value: RequestMappingData) => { + if (key === RequestType.SERVER_SIDE_RPC) { + delete (value as ServerSideRpc).type; + } + + const { attributeNameExpression, deviceInfo, ...rest } = value as AttributeRequest; + + const newValue = { + ...rest, + attributeNameJsonExpression: attributeNameExpression || null, + deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource !== SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, + deviceNameTopicExpression: deviceInfo?.deviceNameExpressionSource === SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, + }; + + this.cleanUpNewFields(newValue); + + return newValue; + }); + + return acc; + }, {}) as Record; + } + + static mapMappingToDowngradedVersion( + mapping: ConverterConnectorMapping[] + ): LegacyConverterConnectorMapping[] { + return mapping?.map((converterMapping: ConverterConnectorMapping) => { + const converter = this.mapConverterToDowngradedVersion(converterMapping.converter); + + this.cleanUpNewFields(converter as {}); + + return { converter, topicFilter: converterMapping.topicFilter }; + }); + } + + private static mapConverterToDowngradedVersion(converter: Converter): LegacyConverter { + const { deviceInfo, ...restConverter } = converter; + + return converter.type !== ConvertorType.BYTES ? { + ...restConverter, + deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource === SourceType.MSG ? deviceInfo.deviceNameExpression : null, + deviceTypeJsonExpression: + deviceInfo?.deviceProfileExpressionSource === SourceType.MSG ? deviceInfo.deviceProfileExpression : null, + deviceNameTopicExpression: + deviceInfo?.deviceNameExpressionSource !== SourceType.MSG + ? deviceInfo?.deviceNameExpression + : null, + deviceTypeTopicExpression: deviceInfo?.deviceProfileExpressionSource !== SourceType.MSG + ? deviceInfo?.deviceProfileExpression + : null, + } : { + ...restConverter, + deviceNameExpression: deviceInfo.deviceNameExpression, + deviceTypeExpression: deviceInfo.deviceProfileExpression, + ['extension-config']: converter.extensionConfig, + }; + } + + private static cleanUpOldFields(obj: Record): void { + this.mqttRequestMappingOldFields.forEach(field => delete obj[field]); + deleteNullProperties(obj); + } + + private static cleanUpNewFields(obj: Record): void { + this.mqttRequestMappingNewFields.forEach(field => delete obj[field]); + deleteNullProperties(obj); + } + + private static getTypeSourceByValue(value: string): SourceType { + if (value.includes('${')) { + return SourceType.MSG; + } + if (value.includes(`/`)) { + return SourceType.TOPIC; + } + return SourceType.CONST; + } + + private static extractConverterDeviceInfo(converter: LegacyConverter): ConnectorDeviceInfo { + const deviceNameExpression = converter.deviceNameExpression + || converter.deviceNameJsonExpression + || converter.deviceNameTopicExpression + || null; + const deviceNameExpressionSource = converter.deviceNameExpressionSource + ? converter.deviceNameExpressionSource as SourceType + : deviceNameExpression ? this.getTypeSourceByValue(deviceNameExpression) : null; + const deviceProfileExpression = converter.deviceProfileExpression + || converter.deviceTypeTopicExpression + || converter.deviceTypeJsonExpression + || 'default'; + const deviceProfileExpressionSource = converter.deviceProfileExpressionSource + ? converter.deviceProfileExpressionSource as SourceType + : deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; + + return deviceNameExpression || deviceProfileExpression ? { + deviceNameExpression, + deviceNameExpressionSource, + deviceProfileExpression, + deviceProfileExpressionSource + } : null; + } + + private static mapRequestToUpgradedVersion(value, key: RequestType): RequestMappingData { + const deviceNameExpression = value.deviceNameJsonExpression || value.deviceNameTopicExpression || null; + const deviceProfileExpression = value.deviceTypeTopicExpression || value.deviceTypeJsonExpression || 'default'; + const deviceProfileExpressionSource = deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; + const attributeNameExpression = value.attributeNameExpressionSource || value.attributeNameJsonExpression || null; + const responseTopicQoS = key === RequestType.SERVER_SIDE_RPC ? 1 : null; + const type = key === RequestType.SERVER_SIDE_RPC + ? (value as ServerSideRpc).responseTopicExpression + ? ServerSideRpcType.WithResponse + : ServerSideRpcType.WithoutResponse + : null; + + return { + ...value, + attributeNameExpression, + attributeNameExpressionSource: attributeNameExpression ? this.getTypeSourceByValue(attributeNameExpression) : null, + deviceInfo: value.deviceInfo ? value.deviceInfo : deviceNameExpression ? { + deviceNameExpression, + deviceNameExpressionSource: this.getTypeSourceByValue(deviceNameExpression), + deviceProfileExpression, + deviceProfileExpressionSource + } : null, + responseTopicQoS, + type + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts new file mode 100644 index 0000000000..35807065e9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts @@ -0,0 +1,134 @@ +/// +/// Copyright © 2016-2024 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 { + Attribute, + AttributesUpdate, + DeviceConnectorMapping, + LegacyAttribute, + LegacyDeviceAttributeUpdate, + LegacyDeviceConnectorMapping, + LegacyRpcMethod, + LegacyServerConfig, + LegacyTimeseries, + OPCBasicConfig_v3_5_2, + OPCUaSourceType, + RpcArgument, + RpcMethod, + ServerConfig, + Timeseries +} from '@home/components/widget/lib/gateway/gateway-widget.models'; + +export class OpcVersionMappingUtil { + + static mapServerToUpgradedVersion(server: LegacyServerConfig): ServerConfig { + const { mapping, disableSubscriptions, ...restServer } = server; + return { + ...restServer, + enableSubscriptions: !disableSubscriptions, + }; + } + + static mapServerToDowngradedVersion(config: OPCBasicConfig_v3_5_2): LegacyServerConfig { + const { mapping, server } = config; + const { enableSubscriptions, ...restServer } = server; + return { + ...restServer, + mapping: mapping ? this.mapMappingToDowngradedVersion(mapping) : [], + disableSubscriptions: !enableSubscriptions, + }; + } + + static mapMappingToUpgradedVersion(mapping: LegacyDeviceConnectorMapping[]): DeviceConnectorMapping[] { + return mapping.map((legacyMapping: LegacyDeviceConnectorMapping) => ({ + ...legacyMapping, + deviceNodeSource: this.getTypeSourceByValue(legacyMapping.deviceNodePattern), + deviceInfo: { + deviceNameExpression: legacyMapping.deviceNamePattern, + deviceNameExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceNamePattern), + deviceProfileExpression: legacyMapping.deviceTypePattern ?? 'default', + deviceProfileExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceTypePattern ?? 'default'), + }, + attributes: legacyMapping.attributes.map((attribute: LegacyAttribute) => ({ + key: attribute.key, + type: this.getTypeSourceByValue(attribute.path), + value: attribute.path, + })), + attributes_updates: legacyMapping.attributes_updates.map((attributeUpdate: LegacyDeviceAttributeUpdate) => ({ + key: attributeUpdate.attributeOnThingsBoard, + type: this.getTypeSourceByValue(attributeUpdate.attributeOnDevice), + value: attributeUpdate.attributeOnDevice, + })), + timeseries: legacyMapping.timeseries.map((timeseries: LegacyTimeseries) => ({ + key: timeseries.key, + type: this.getTypeSourceByValue(timeseries.path), + value: timeseries.path, + })), + rpc_methods: legacyMapping.rpc_methods.map((rpcMethod: LegacyRpcMethod) => ({ + method: rpcMethod.method, + arguments: rpcMethod.arguments.map(arg => ({ + value: arg, + type: this.getArgumentType(arg), + } as RpcArgument)) + })) + })); + } + + static mapMappingToDowngradedVersion(mapping: DeviceConnectorMapping[]): LegacyDeviceConnectorMapping[] { + return mapping.map((upgradedMapping: DeviceConnectorMapping) => ({ + ...upgradedMapping, + deviceNamePattern: upgradedMapping.deviceInfo.deviceNameExpression, + deviceTypePattern: upgradedMapping.deviceInfo.deviceProfileExpression, + attributes: upgradedMapping.attributes.map((attribute: Attribute) => ({ + key: attribute.key, + path: attribute.value, + })), + attributes_updates: upgradedMapping.attributes_updates.map((attributeUpdate: AttributesUpdate) => ({ + attributeOnThingsBoard: attributeUpdate.key, + attributeOnDevice: attributeUpdate.value, + })), + timeseries: upgradedMapping.timeseries.map((timeseries: Timeseries) => ({ + key: timeseries.key, + path: timeseries.value, + })), + rpc_methods: upgradedMapping.rpc_methods.map((rpcMethod: RpcMethod) => ({ + method: rpcMethod.method, + arguments: rpcMethod.arguments.map((arg: RpcArgument) => arg.value) + })) + })); + } + + private static getTypeSourceByValue(value: string): OPCUaSourceType { + if (value.includes('${')) { + return OPCUaSourceType.IDENTIFIER; + } + if (value.includes(`/`) || value.includes('\\')) { + return OPCUaSourceType.PATH; + } + return OPCUaSourceType.CONST; + } + + private static getArgumentType(arg: unknown): string { + switch (typeof arg) { + case 'boolean': + return 'boolean'; + case 'number': + return Number.isInteger(arg) ? 'integer' : 'float'; + default: + return 'string'; + } + } +} 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 2acc299586..f241bc91d0 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 @@ -113,22 +113,22 @@ import { GatewayHelpLinkPipe } from '@home/components/widget/lib/gateway/pipes/g import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; import { BrokerConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; import { WorkersConfigControlComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; import { OpcServerConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; import { MqttBasicConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component'; import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; import { OpcUaBasicConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component'; +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component'; import { ModbusBasicConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component'; @@ -145,12 +145,21 @@ import { ModbusRpcParametersComponent } from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component'; import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component'; +import { + MqttLegacyBasicConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component'; import { GatewayBasicConfigurationComponent } from '@home/components/widget/lib/gateway/configuration/basic/gateway-basic-configuration.component'; import { GatewayAdvancedConfigurationComponent } from '@home/components/widget/lib/gateway/configuration/advanced/gateway-advanced-configuration.component'; +import { + OpcUaLegacyBasicConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component'; +import { + ModbusLegacyBasicConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component'; @NgModule({ declarations: [ @@ -239,8 +248,11 @@ import { ModbusBasicConfigComponent, EllipsisChipListDirective, ModbusRpcParametersComponent, + MqttLegacyBasicConfigComponent, GatewayBasicConfigurationComponent, GatewayAdvancedConfigurationComponent, + OpcUaLegacyBasicConfigComponent, + ModbusLegacyBasicConfigComponent, ], exports: [ EntitiesTableWidgetComponent, diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index 1e7eef1c41..66a704996b 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -22,7 +22,7 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { Resolve, Router } from '@angular/router'; -import { Resource, ResourceInfo, ResourceTypeTranslationMap } from '@shared/models/resource.models'; +import { Resource, ResourceInfo, ResourceType, ResourceTypeTranslationMap } from '@shared/models/resource.models'; import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { DatePipe } from '@angular/common'; @@ -118,7 +118,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve this.isResourceEditable(resource, authUser.authority); this.config.entitySelectionEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); - this.config.detailsReadonly = (resource) => !this.isResourceEditable(resource, authUser.authority); + this.config.detailsReadonly = (resource) => this.detailsReadonly(resource, authUser.authority); return this.config; } @@ -149,6 +149,13 @@ export class ResourcesLibraryTableConfigResolver implements Resolve resource.title - + {{ 'resource.title-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts index f6a8befe9b..de6a62e15c 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts @@ -43,8 +43,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme readonly resourceType = ResourceType; readonly resourceTypes: ResourceType[] = Object.values(this.resourceType); readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; - - maxResourceSize = getCurrentAuthState(this.store).maxResourceSize; + readonly maxResourceSize = getCurrentAuthState(this.store).maxResourceSize; private destroy$ = new Subject(); @@ -57,20 +56,20 @@ export class ResourcesLibraryComponent extends EntityComponent impleme super(store, fb, entityValue, entitiesTableConfigValue, cd); } - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); if (this.isAdd) { this.observeResourceTypeChange(); } } - ngOnDestroy() { + ngOnDestroy(): void { super.ngOnDestroy(); this.destroy$.next(); this.destroy$.complete(); } - hideDelete() { + hideDelete(): boolean { if (this.entitiesTableConfig) { return !this.entitiesTableConfig.deleteEnabled(this.entity); } else { @@ -87,19 +86,18 @@ export class ResourcesLibraryComponent extends EntityComponent impleme }); } - updateForm(entity: Resource) { - if (this.isEdit) { - this.entityForm.get('resourceType').disable({emitEvent: false}); - if (entity.resourceType !== ResourceType.JS_MODULE) { - this.entityForm.get('fileName').disable({emitEvent: false}); + updateForm(entity: Resource): void { + this.entityForm.patchValue(entity); + } + + override updateFormState(): void { + super.updateFormState(); + if (this.isEdit && this.entityForm) { + this.entityForm.get('resourceType').disable({ emitEvent: false }); + if (this.entityForm.get('resourceType').value !== ResourceType.JS_MODULE) { + this.entityForm.get('fileName').disable({ emitEvent: false }); } } - this.entityForm.patchValue({ - resourceType: entity.resourceType, - fileName: entity.fileName, - title: entity.title, - data: entity.data - }); } prepareFormValue(formValue: Resource): Resource { @@ -109,7 +107,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme return super.prepareFormValue(formValue); } - getAllowedExtensions() { + getAllowedExtensions(): string { try { return ResourceTypeExtension.get(this.entityForm.get('resourceType').value); } catch (e) { @@ -117,7 +115,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme } } - getAcceptType() { + getAcceptType(): string { try { return ResourceTypeMIMETypes.get(this.entityForm.get('resourceType').value); } catch (e) { @@ -129,7 +127,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme return window.btoa(data); } - onResourceIdCopied() { + onResourceIdCopied(): void { this.store.dispatch(new ActionNotificationShow( { message: this.translate.instant('resource.idCopiedMessage'), diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 829e49257f..b43081c6e8 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -161,7 +161,7 @@ export const HelpLinks = { assetProfiles: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/asset-profiles`, edges: `${helpBaseUrl}/docs/edge/getting-started-guides/what-is-edge`, assets: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/assets`, - entityViews: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/entity-views`, + entityViews: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/entity-views`, entitiesImport: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/bulk-provisioning`, rulechains: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/rule-chains`, lwm2mResourceLibrary: `${helpBaseUrl}/docs${docPlatformPrefix}/reference/lwm2m-api`, diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json index 441f63ab5f..38bb44e036 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json @@ -1,246 +1,474 @@ { - "master": { - "slaves": [ - { - "name": "Slave 1", - "host": "127.0.0.1", - "port": 5021, - "type": "tcp", - "method": "socket", - "timeout": 35, - "byteOrder": "LITTLE", - "wordOrder": "LITTLE", - "retries": true, - "retryOnEmpty": true, - "retryOnInvalid": true, - "pollPeriod": 5000, - "unitId": 1, - "deviceName": "Temp Sensor", - "deviceType": "default", - "sendDataOnlyOnChange": true, - "connectAttemptTimeMs": 5000, - "connectAttemptCount": 5, - "waitAfterFailedAttemptsMs": 300000, - "attributes": [ - { - "tag": "string_read", - "type": "string", - "functionCode": 4, - "objectsCount": 4, - "address": 1 - }, - { - "tag": "bits_read", - "type": "bits", - "functionCode": 4, - "objectsCount": 1, - "address": 5 - }, - { - "tag": "8int_read", - "type": "8int", - "functionCode": 4, - "objectsCount": 1, - "address": 6 - }, - { - "tag": "16int_read", - "type": "16int", - "functionCode": 4, - "objectsCount": 1, - "address": 7 - }, - { - "tag": "32int_read_divider", - "type": "32int", - "functionCode": 4, - "objectsCount": 2, - "address": 8, - "divider": 10 - }, - { - "tag": "8int_read_multiplier", - "type": "8int", - "functionCode": 4, - "objectsCount": 1, - "address": 10, - "multiplier": 10 - }, - { - "tag": "32int_read", - "type": "32int", - "functionCode": 4, - "objectsCount": 2, - "address": 11 - }, - { - "tag": "64int_read", - "type": "64int", - "functionCode": 4, - "objectsCount": 4, - "address": 13 - } - ], - "timeseries": [ - { - "tag": "8uint_read", - "type": "8uint", - "functionCode": 4, - "objectsCount": 1, - "address": 17 - }, - { - "tag": "16uint_read", - "type": "16uint", - "functionCode": 4, - "objectsCount": 2, - "address": 18 - }, - { - "tag": "32uint_read", - "type": "32uint", - "functionCode": 4, - "objectsCount": 4, - "address": 20 - }, - { - "tag": "64uint_read", - "type": "64uint", - "functionCode": 4, - "objectsCount": 1, - "address": 24 - }, - { - "tag": "16float_read", - "type": "16float", - "functionCode": 4, - "objectsCount": 1, - "address": 25 - }, - { - "tag": "32float_read", - "type": "32float", - "functionCode": 4, - "objectsCount": 2, - "address": 26 - }, - { - "tag": "64float_read", - "type": "64float", - "functionCode": 4, - "objectsCount": 4, - "address": 28 - } - ], - "attributeUpdates": [ - { - "tag": "shared_attribute_write", - "type": "32int", - "functionCode": 6, - "objectsCount": 2, - "address": 29 - } - ], - "rpc": [ - { - "tag": "setValue", - "type": "bits", - "functionCode": 5, - "objectsCount": 1, - "address": 31 - }, - { - "tag": "getValue", - "type": "bits", - "functionCode": 1, - "objectsCount": 1, - "address": 31 - }, - { - "tag": "setCPUFanSpeed", - "type": "32int", - "functionCode": 16, - "objectsCount": 2, - "address": 33 - }, - { - "tag": "getCPULoad", - "type": "32int", - "functionCode": 4, - "objectsCount": 2, - "address": 35 - } - ] - } - ] - }, - "slave": { - "type": "tcp", - "host": "127.0.0.1", - "port": 5026, - "method": "socket", - "deviceName": "Modbus Slave Example", - "deviceType": "default", - "pollPeriod": 5000, - "sendDataToThingsBoard": false, - "byteOrder": "LITTLE", - "wordOrder": "LITTLE", - "unitId": 0, - "values": { - "holding_registers": { - "attributes": [ + "3.5.2": { + "master": { + "slaves": [ { - "address": 1, - "type": "string", - "tag": "sm", - "objectsCount": 1, - "value": "ON" + "name": "Slave 1", + "host": "127.0.0.1", + "port": 5021, + "type": "tcp", + "method": "socket", + "timeout": 35, + "byteOrder": "LITTLE", + "wordOrder": "LITTLE", + "retries": true, + "retryOnEmpty": true, + "retryOnInvalid": true, + "pollPeriod": 5000, + "unitId": 1, + "deviceName": "Temp Sensor", + "deviceType": "default", + "sendDataOnlyOnChange": true, + "connectAttemptTimeMs": 5000, + "connectAttemptCount": 5, + "waitAfterFailedAttemptsMs": 300000, + "attributes": [ + { + "tag": "string_read", + "type": "string", + "functionCode": 4, + "objectsCount": 4, + "address": 1 + }, + { + "tag": "bits_read", + "type": "bits", + "functionCode": 4, + "objectsCount": 1, + "address": 5 + }, + { + "tag": "8int_read", + "type": "8int", + "functionCode": 4, + "objectsCount": 1, + "address": 6 + }, + { + "tag": "16int_read", + "type": "16int", + "functionCode": 4, + "objectsCount": 1, + "address": 7 + }, + { + "tag": "32int_read_divider", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 8, + "divider": 10 + }, + { + "tag": "8int_read_multiplier", + "type": "8int", + "functionCode": 4, + "objectsCount": 1, + "address": 10, + "multiplier": 10 + }, + { + "tag": "32int_read", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 11 + }, + { + "tag": "64int_read", + "type": "64int", + "functionCode": 4, + "objectsCount": 4, + "address": 13 + } + ], + "timeseries": [ + { + "tag": "8uint_read", + "type": "8uint", + "functionCode": 4, + "objectsCount": 1, + "address": 17 + }, + { + "tag": "16uint_read", + "type": "16uint", + "functionCode": 4, + "objectsCount": 2, + "address": 18 + }, + { + "tag": "32uint_read", + "type": "32uint", + "functionCode": 4, + "objectsCount": 4, + "address": 20 + }, + { + "tag": "64uint_read", + "type": "64uint", + "functionCode": 4, + "objectsCount": 1, + "address": 24 + }, + { + "tag": "16float_read", + "type": "16float", + "functionCode": 4, + "objectsCount": 1, + "address": 25 + }, + { + "tag": "32float_read", + "type": "32float", + "functionCode": 4, + "objectsCount": 2, + "address": 26 + }, + { + "tag": "64float_read", + "type": "64float", + "functionCode": 4, + "objectsCount": 4, + "address": 28 + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31 + }, + { + "tag": "getValue", + "type": "bits", + "functionCode": 1, + "objectsCount": 1, + "address": 31 + }, + { + "tag": "setCPUFanSpeed", + "type": "32int", + "functionCode": 16, + "objectsCount": 2, + "address": 33 + }, + { + "tag": "getCPULoad", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 35 + } + ] } - ], - "timeseries": [ - { - "address": 2, - "type": "8int", - "tag": "smm", - "objectsCount": 1, - "value": "12334" - } - ], - "attributeUpdates": [ - { - "tag": "shared_attribute_write", - "type": "32int", - "functionCode": 6, - "objectsCount": 2, - "address": 29, - "value": 1243 + ] + }, + "slave": { + "type": "tcp", + "host": "127.0.0.1", + "port": 5026, + "method": "socket", + "deviceName": "Modbus Slave Example", + "deviceType": "default", + "pollPeriod": 5000, + "sendDataToThingsBoard": false, + "byteOrder": "LITTLE", + "wordOrder": "LITTLE", + "unitId": 0, + "values": { + "holding_registers": { + "attributes": [ + { + "address": 1, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "ON" + } + ], + "timeseries": [ + { + "address": 2, + "type": "8int", + "tag": "smm", + "objectsCount": 1, + "value": "12334" + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29, + "value": 1243 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31, + "value": 22 + } + ] + }, + "coils_initializer": { + "attributes": [ + { + "address": 5, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "12" + } + ], + "timeseries": [], + "attributeUpdates": [], + "rpc": [] } - ], - "rpc": [ + } + } + }, + "legacy": { + "master": { + "slaves": [ { - "tag": "setValue", - "type": "bits", - "functionCode": 5, - "objectsCount": 1, - "address": 31, - "value": 22 + "host": "127.0.0.1", + "port": 5021, + "type": "tcp", + "method": "socket", + "timeout": 35, + "byteOrder": "LITTLE", + "wordOrder": "LITTLE", + "retries": true, + "retryOnEmpty": true, + "retryOnInvalid": true, + "pollPeriod": 5000, + "unitId": 1, + "deviceName": "Temp Sensor", + "sendDataOnlyOnChange": true, + "connectAttemptTimeMs": 5000, + "connectAttemptCount": 5, + "waitAfterFailedAttemptsMs": 300000, + "attributes": [ + { + "tag": "string_read", + "type": "string", + "functionCode": 4, + "objectsCount": 4, + "address": 1 + }, + { + "tag": "bits_read", + "type": "bits", + "functionCode": 4, + "objectsCount": 1, + "address": 5 + }, + { + "tag": "16int_read", + "type": "16int", + "functionCode": 4, + "objectsCount": 1, + "address": 7 + }, + { + "tag": "32int_read_divider", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 8, + "divider": 10 + }, + { + "tag": "32int_read", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 11 + }, + { + "tag": "64int_read", + "type": "64int", + "functionCode": 4, + "objectsCount": 4, + "address": 13 + } + ], + "timeseries": [ + { + "tag": "16uint_read", + "type": "16uint", + "functionCode": 4, + "objectsCount": 2, + "address": 18 + }, + { + "tag": "32uint_read", + "type": "32uint", + "functionCode": 4, + "objectsCount": 4, + "address": 20 + }, + { + "tag": "64uint_read", + "type": "64uint", + "functionCode": 4, + "objectsCount": 1, + "address": 24 + }, + { + "tag": "16float_read", + "type": "16float", + "functionCode": 4, + "objectsCount": 1, + "address": 25 + }, + { + "tag": "32float_read", + "type": "32float", + "functionCode": 4, + "objectsCount": 2, + "address": 26 + }, + { + "tag": "64float_read", + "type": "64float", + "functionCode": 4, + "objectsCount": 4, + "address": 28 + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31 + }, + { + "tag": "getValue", + "type": "bits", + "functionCode": 1, + "objectsCount": 1, + "address": 31 + }, + { + "tag": "setCPUFanSpeed", + "type": "32int", + "functionCode": 16, + "objectsCount": 2, + "address": 33 + }, + { + "tag": "getCPULoad", + "type": "32int", + "functionCode": 4, + "objectsCount": 2, + "address": 35 + } + ] } ] }, - "coils_initializer": { - "attributes": [ - { - "address": 5, - "type": "string", - "tag": "sm", - "objectsCount": 1, - "value": "12" - } - ], - "timeseries": [], - "attributeUpdates": [], - "rpc": [] + "slave": { + "type": "tcp", + "host": "127.0.0.1", + "port": 5026, + "method": "socket", + "deviceName": "Modbus Slave Example", + "deviceType": "default", + "pollPeriod": 5000, + "sendDataToThingsBoard": false, + "byteOrder": "LITTLE", + "wordOrder": "LITTLE", + "unitId": 0, + "values": { + "holding_registers": [ + { + "attributes": [ + { + "address": 1, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "ON" + } + ], + "timeseries": [ + { + "address": 2, + "type": "int", + "tag": "smm", + "objectsCount": 1, + "value": "12334" + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29, + "value": 1243 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31, + "value": 22 + } + ] + } + ], + "coils_initializer": [ + { + "attributes": [ + { + "address": 5, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "12" + } + ], + "timeseries": [], + "attributeUpdates": [], + "rpc": [] + } + ] + } } } - } } diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json index 3e0800c5d6..5b8b8dbd44 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json @@ -1,217 +1,398 @@ { - "broker": { - "host": "127.0.0.1", - "port": 1883, - "clientId": "ThingsBoard_gateway", - "version": 5, - "maxMessageNumberPerWorker": 10, - "maxNumberOfWorkers": 100, - "sendDataOnlyOnChange": false, - "security": { - "type": "anonymous" - } - }, - "dataMapping": [ - { - "topicFilter": "sensor/data", - "subscriptionQos": 1, - "converter": { - "type": "json", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "${serialNumber}", - "deviceProfileExpressionSource": "message", - "deviceProfileExpression": "${sensorType}" + "3.5.2": { + "broker": { + "host": "127.0.0.1", + "port": 1883, + "clientId": "ThingsBoard_gateway", + "version": 5, + "maxMessageNumberPerWorker": 10, + "maxNumberOfWorkers": 100, + "sendDataOnlyOnChange": false, + "security": { + "type": "anonymous" + } }, - "sendDataOnlyOnChange": false, - "timeout": 60000, - "attributes": [ - { - "type": "string", - "key": "model", - "value": "${sensorModel}" - }, - { - "type": "string", - "key": "${sensorModel}", - "value": "on" - } + "mapping": [ + { + "topicFilter": "sensor/data", + "subscriptionQos": 1, + "converter": { + "type": "json", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "${serialNumber}", + "deviceProfileExpressionSource": "message", + "deviceProfileExpression": "${sensorType}" + }, + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + }, + { + "type": "string", + "key": "${sensorModel}", + "value": "on" + } + ], + "timeseries": [ + { + "type": "string", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "double", + "key": "humidity", + "value": "${hum}" + }, + { + "type": "string", + "key": "combine", + "value": "${hum}:${temp}" + } + ] + } + }, + { + "topicFilter": "sensor/+/data", + "subscriptionQos": 1, + "converter": { + "type": "json", + "deviceInfo": { + "deviceNameExpressionSource": "topic", + "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/data)", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "Thermometer" + }, + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "string", + "key": "humidity", + "value": "${hum}" + } + ] + } + }, + { + "topicFilter": "sensor/raw_data", + "subscriptionQos": 1, + "converter": { + "type": "bytes", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "[0:4]", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "default" + }, + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "raw", + "key": "rawData", + "value": "[:]" + } + ], + "timeseries": [ + { + "type": "raw", + "key": "temp", + "value": "[4:]" + } + ] + } + }, + { + "topicFilter": "custom/sensors/+", + "subscriptionQos": 1, + "converter": { + "type": "custom", + "extension": "CustomMqttUplinkConverter", + "cached": true, + "extensionConfig": { + "temperature": 2, + "humidity": 2, + "batteryLevel": 1 + } + } + } ], - "timeseries": [ - { - "type": "string", - "key": "temperature", - "value": "${temp}" - }, - { - "type": "double", - "key": "humidity", - "value": "${hum}" - }, - { - "type": "string", - "key": "combine", - "value": "${hum}:${temp}" - } - ] - } + "requestsMapping": { + "connectRequests": [ + { + "topicFilter": "sensor/connect", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "${serialNumber}", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "Thermometer" + } + }, + { + "topicFilter": "sensor/+/connect", + "deviceInfo": { + "deviceNameExpressionSource": "topic", + "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "Thermometer" + } + } + ], + "disconnectRequests": [ + { + "topicFilter": "sensor/disconnect", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "${serialNumber}" + } + }, + { + "topicFilter": "sensor/+/disconnect", + "deviceInfo": { + "deviceNameExpressionSource": "topic", + "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)" + } + } + ], + "attributeRequests": [ + { + "retain": false, + "topicFilter": "v1/devices/me/attributes/request", + "deviceInfo": { + "deviceNameExpressionSource": "message", + "deviceNameExpression": "${serialNumber}" + }, + "attributeNameExpressionSource": "message", + "attributeNameExpression": "${versionAttribute}, ${pduAttribute}", + "topicExpression": "devices/${deviceName}/attrs", + "valueExpression": "${attributeKey}: ${attributeValue}" + } + ], + "attributeUpdates": [ + { + "retain": true, + "deviceNameFilter": ".*", + "attributeFilter": "firmwareVersion", + "topicExpression": "sensor/${deviceName}/${attributeKey}", + "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" + } + ], + "serverSideRpc": [ + { + "type": "twoWay", + "deviceNameFilter": ".*", + "methodFilter": "echo", + "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", + "responseTopicQoS": 1, + "responseTimeout": 10000, + "valueExpression": "${params}" + }, + { + "type": "oneWay", + "deviceNameFilter": ".*", + "methodFilter": "no-reply", + "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "valueExpression": "${params}" + } + ] + } }, - { - "topicFilter": "sensor/+/data", - "subscriptionQos": 1, - "converter": { - "type": "json", - "deviceInfo": { - "deviceNameExpressionSource": "topic", - "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/data)", - "deviceProfileExpressionSource": "constant", - "deviceProfileExpression": "Thermometer" + "legacy": { + "broker": { + "name": "Default Local Broker", + "host": "127.0.0.1", + "port": 1883, + "clientId": "ThingsBoard_gateway", + "version": 5, + "maxMessageNumberPerWorker": 10, + "maxNumberOfWorkers": 100, + "sendDataOnlyOnChange": false, + "security": { + "type": "basic", + "username": "user", + "password": "password" + } }, - "sendDataOnlyOnChange": false, - "timeout": 60000, - "attributes": [ - { - "type": "string", - "key": "model", - "value": "${sensorModel}" - } + "mapping": [ + { + "topicFilter": "sensor/data", + "converter": { + "type": "json", + "deviceNameJsonExpression": "${serialNumber}", + "deviceTypeJsonExpression": "${sensorType}", + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + }, + { + "type": "string", + "key": "${sensorModel}", + "value": "on" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "double", + "key": "humidity", + "value": "${hum}" + }, + { + "type": "string", + "key": "combine", + "value": "${hum}:${temp}" + } + ] + } + }, + { + "topicFilter": "sensor/+/data", + "converter": { + "type": "json", + "deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/data)", + "deviceTypeTopicExpression": "Thermometer", + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "string", + "key": "model", + "value": "${sensorModel}" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, + { + "type": "double", + "key": "humidity", + "value": "${hum}" + } + ] + } + }, + { + "topicFilter": "sensor/raw_data", + "converter": { + "type": "bytes", + "deviceNameExpression": "[0:4]", + "deviceTypeExpression": "default", + "sendDataOnlyOnChange": false, + "timeout": 60000, + "attributes": [ + { + "type": "raw", + "key": "rawData", + "value": "[:]" + } + ], + "timeseries": [ + { + "type": "raw", + "key": "temp", + "value": "[4:]" + } + ] + } + }, + { + "topicFilter": "custom/sensors/+", + "converter": { + "type": "custom", + "extension": "CustomMqttUplinkConverter", + "cached": true, + "extension-config": { + "temperatureBytes": 2, + "humidityBytes": 2, + "batteryLevelBytes": 1 + } + } + } ], - "timeseries": [ - { - "type": "double", - "key": "temperature", - "value": "${temp}" - }, - { - "type": "string", - "key": "humidity", - "value": "${hum}" - } - ] - } - }, - { - "topicFilter": "sensor/raw_data", - "subscriptionQos": 1, - "converter": { - "type": "bytes", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "[0:4]", - "deviceProfileExpressionSource": "constant", - "deviceProfileExpression": "default" - }, - "sendDataOnlyOnChange": false, - "timeout": 60000, - "attributes": [ - { - "type": "raw", - "key": "rawData", - "value": "[:]" - } + "connectRequests": [ + { + "topicFilter": "sensor/connect", + "deviceNameJsonExpression": "${serialNumber}" + }, + { + "topicFilter": "sensor/+/connect", + "deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/connect)" + } + ], + "disconnectRequests": [ + { + "topicFilter": "sensor/disconnect", + "deviceNameJsonExpression": "${serialNumber}" + }, + { + "topicFilter": "sensor/+/disconnect", + "deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/disconnect)" + } + ], + "attributeRequests": [ + { + "retain": false, + "topicFilter": "v1/devices/me/attributes/request", + "deviceNameJsonExpression": "${serialNumber}", + "attributeNameJsonExpression": "${versionAttribute}, ${pduAttribute}", + "topicExpression": "devices/${deviceName}/attrs", + "valueExpression": "${attributeKey}: ${attributeValue}" + } ], - "timeseries": [ - { - "type": "raw", - "key": "temp", - "value": "[4:]" - } + "attributeUpdates": [ + { + "retain": true, + "deviceNameFilter": ".*", + "attributeFilter": "firmwareVersion", + "topicExpression": "sensor/${deviceName}/${attributeKey}", + "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" + } + ], + "serverSideRpc": [ + { + "deviceNameFilter": ".*", + "methodFilter": "echo", + "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", + "responseTimeout": 10000, + "valueExpression": "${params}" + }, + { + "deviceNameFilter": ".*", + "methodFilter": "no-reply", + "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", + "valueExpression": "${params}" + } ] - } - }, - { - "topicFilter": "custom/sensors/+", - "subscriptionQos": 1, - "converter": { - "type": "custom", - "extension": "CustomMqttUplinkConverter", - "cached": true, - "extensionConfig": { - "temperature": 2, - "humidity": 2, - "batteryLevel": 1 - } - } - } - ], - "requestsMapping": { - "connectRequests": [ - { - "topicFilter": "sensor/connect", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "${serialNumber}", - "deviceProfileExpressionSource": "constant", - "deviceProfileExpression": "Thermometer" - } - }, - { - "topicFilter": "sensor/+/connect", - "deviceInfo": { - "deviceNameExpressionSource": "topic", - "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)", - "deviceProfileExpressionSource": "constant", - "deviceProfileExpression": "Thermometer" - } - } - ], - "disconnectRequests": [ - { - "topicFilter": "sensor/disconnect", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "${serialNumber}" - } - }, - { - "topicFilter": "sensor/+/disconnect", - "deviceInfo": { - "deviceNameExpressionSource": "topic", - "deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)" - } - } - ], - "attributeRequests": [ - { - "retain": false, - "topicFilter": "v1/devices/me/attributes/request", - "deviceInfo": { - "deviceNameExpressionSource": "message", - "deviceNameExpression": "${serialNumber}" - }, - "attributeNameExpressionSource": "message", - "attributeNameExpression": "${versionAttribute}, ${pduAttribute}", - "topicExpression": "devices/${deviceName}/attrs", - "valueExpression": "${attributeKey}: ${attributeValue}" - } - ], - "attributeUpdates": [ - { - "retain": true, - "deviceNameFilter": ".*", - "attributeFilter": "firmwareVersion", - "topicExpression": "sensor/${deviceName}/${attributeKey}", - "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" - } - ], - "serverSideRpc": [ - { - "type": "twoWay", - "deviceNameFilter": ".*", - "methodFilter": "echo", - "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", - "responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", - "responseTopicQoS": 1, - "responseTimeout": 10000, - "valueExpression": "${params}" - }, - { - "type": "oneWay", - "deviceNameFilter": ".*", - "methodFilter": "no-reply", - "requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", - "valueExpression": "${params}" } - ] - } } diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json index 7ff5b78433..c0ef993dd1 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/opcua.json @@ -1,66 +1,120 @@ { - "server": { - "url": "localhost:4840/freeopcua/server/", - "timeoutInMillis": 5000, - "scanPeriodInMillis": 3600000, - "pollPeriodInMillis": 5000, - "enableSubscriptions": true, - "subCheckPeriodInMillis": 100, - "showMap": false, - "security": "Basic128Rsa15", - "identity": { - "type": "anonymous" - } - }, - "mapping": [{ - "deviceNodePattern": "Root\\.Objects\\.Device1", - "deviceNodeSource": "path", - "deviceInfo": { - "deviceNameExpression": "Device ${Root\\.Objects\\.Device1\\.serialNumber}", - "deviceNameExpressionSource": "path", - "deviceProfileExpression": "Device", - "deviceProfileExpressionSource": "constant" + "3.5.2": { + "server": { + "url": "localhost:4840/freeopcua/server/", + "timeoutInMillis": 5000, + "scanPeriodInMillis": 3600000, + "pollPeriodInMillis": 5000, + "enableSubscriptions": true, + "subCheckPeriodInMillis": 100, + "showMap": false, + "security": "Basic128Rsa15", + "identity": { + "type": "anonymous" + } + }, + "mapping": [{ + "deviceNodePattern": "Root\\.Objects\\.Device1", + "deviceNodeSource": "path", + "deviceInfo": { + "deviceNameExpression": "Device ${Root\\.Objects\\.Device1\\.serialNumber}", + "deviceNameExpressionSource": "path", + "deviceProfileExpression": "Device", + "deviceProfileExpressionSource": "constant" + }, + "attributes": [ + { + "key": "temperature °C", + "type": "path", + "value": "${ns=2;i=5}" + } + ], + "timeseries": [ + { + "key": "humidity", + "type": "path", + "value": "${Root\\.Objects\\.Device1\\.TemperatureAndHumiditySensor\\.Humidity}" + }, + { + "key": "batteryLevel", + "type": "path", + "value": "${Battery\\.batteryLevel}" + } + ], + "rpc_methods": [ + { + "method": "multiply", + "arguments": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 4 + } + ] + } + ], + "attributes_updates": [ + { + "key": "deviceName", + "type": "path", + "value": "Root\\.Objects\\.Device1\\.serialNumber" + } + ] + }] }, - "attributes": [ - { - "key": "temperature °C", - "type": "path", - "value": "${ns=2;i=5}" - } - ], - "timeseries": [ - { - "key": "humidity", - "type": "path", - "value": "${Root\\.Objects\\.Device1\\.TemperatureAndHumiditySensor\\.Humidity}" - }, - { - "key": "batteryLevel", - "type": "path", - "value": "${Battery\\.batteryLevel}" - } - ], - "rpc_methods": [ - { - "method": "multiply", - "arguments": [ - { - "type": "integer", - "value": 2 - }, - { - "type": "integer", - "value": 4 - } - ] - } - ], - "attributes_updates": [ - { - "key": "deviceName", - "type": "path", - "value": "Root\\.Objects\\.Device1\\.serialNumber" - } - ] - }] + "legacy": { + "server": { + "name": "OPC-UA Default Server", + "url": "localhost:4840/freeopcua/server/", + "timeoutInMillis": 5000, + "scanPeriodInMillis": 5000, + "disableSubscriptions": false, + "subCheckPeriodInMillis": 100, + "showMap": false, + "security": "Basic128Rsa15", + "identity": { + "type": "anonymous" + }, + "mapping": [ + { + "deviceNodePattern": "Root\\.Objects\\.Device1", + "deviceNamePattern": "Device ${Root\\.Objects\\.Device1\\.serialNumber}", + "attributes": [ + { + "key": "temperature °C", + "path": "${ns=2;i=5}" + } + ], + "timeseries": [ + { + "key": "humidity", + "path": "${Root\\.Objects\\.Device1\\.TemperatureAndHumiditySensor\\.Humidity}" + }, + { + "key": "batteryLevel", + "path": "${Battery\\.batteryLevel}" + } + ], + "rpc_methods": [ + { + "method": "multiply", + "arguments": [ + 2, + 4 + ] + } + ], + "attributes_updates": [ + { + "attributeOnThingsBoard": "deviceName", + "attributeOnDevice": "Root\\.Objects\\.Device1\\.serialNumber" + } + ] + } + ] + } + } }