Browse Source

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

pull/11641/head
Igor Kulikov 2 years ago
parent
commit
bcdf0393a3
  1. 4
      application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg
  2. 5
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  3. 2
      application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java
  4. 59
      application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java
  5. 44
      application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java
  6. 58
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionJsonDeviceTest.java
  7. 45
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/provision/MqttProvisionProtoDeviceTest.java
  8. 1
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java
  9. 1
      common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java
  10. 1
      common/proto/src/main/proto/queue.proto
  11. 7
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  12. 41
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
  13. 2
      ui-ngx/src/app/core/http/ota-package.service.ts
  14. 72
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract.ts
  15. 61
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts
  16. 68
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts
  17. 101
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract.ts
  18. 56
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/opc-version-processor.abstract.ts
  19. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/configuration/models/gateway-configuration.models.ts
  20. 6
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts
  21. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts
  22. 23
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts
  23. 81
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts
  24. 110
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts
  25. 76
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts
  26. 191
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts
  27. 82
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts
  28. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html
  29. 0
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss
  30. 103
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts
  31. 124
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts
  32. 0
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html
  33. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts
  34. 0
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html
  35. 0
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts
  36. 134
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts
  37. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.html
  38. 0
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss
  39. 15
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.ts
  40. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.html
  41. 0
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss
  42. 88
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.ts
  43. 88
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-legacy-basic-config.component.ts
  44. 24
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts
  45. 27
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts
  46. 43
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html
  47. 5
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss
  48. 32
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  49. 288
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  50. 10
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts
  51. 42
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts
  52. 80
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts
  53. 217
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts
  54. 134
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts
  55. 22
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  56. 11
      ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts
  57. 2
      ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html
  58. 36
      ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts
  59. 2
      ui-ngx/src/app/shared/models/constants.ts
  60. 698
      ui-ngx/src/assets/metadata/connector-default-configs/modbus.json
  61. 595
      ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json
  62. 180
      ui-ngx/src/assets/metadata/connector-default-configs/opcua.json

4
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;"
}
},

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

5
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);
}
}

2
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) {

59
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;
}
}

44
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();
}

58
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;
}
}

45
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();
}

1
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;
}

1
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();
}

1
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 {

7
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<DeviceCacheK
device.setName(provisionRequest.getDeviceName());
device.setType(profile.getName());
device.setTenantId(profile.getTenantId());
if (provisionRequest.getGateway() != null && provisionRequest.getGateway()) {
ObjectNode additionalInfoNode = JacksonUtil.newObjectNode();
additionalInfoNode.put(DataConstants.GATEWAY_PARAMETER, true);
device.setAdditionalInfo(additionalInfoNode);
}
Device savedDevice = saveDevice(device);
if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) ||
!StringUtils.isEmpty(provisionRequest.getCredentialsData().getX509CertHash()) ||

41
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java

@ -21,6 +21,7 @@ import io.restassured.path.json.JsonPath;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
@ -156,6 +157,46 @@ public class HttpClientTest extends AbstractContainerTest {
updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED);
}
@Test
public void provisionRequestForGatewayDeviceWithAllowToCreateNewDevicesStrategy() throws Exception {
String testDeviceName = "test_provision_device";
DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId());
deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
JsonObject provisionRequest = new JsonObject();
provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
provisionRequest.addProperty("deviceName", testDeviceName);
provisionRequest.addProperty("gateway", true);
JsonPath provisionResponse = testRestClient.postProvisionRequest(provisionRequest.toString());
String credentialsType = provisionResponse.get("credentialsType");
String credentialsValue = provisionResponse.get("credentialsValue");
String status = provisionResponse.get("status");
testRestClient.deleteDeviceIfExists(device.getId());
device = testRestClient.getDeviceByName(testDeviceName);
JsonNode additionalInfo = device.getAdditionalInfo();
assertThat(additionalInfo).isNotNull();
assertThat(additionalInfo.has(DataConstants.GATEWAY_PARAMETER)
&& additionalInfo.get(DataConstants.GATEWAY_PARAMETER).isBoolean()).isTrue();
assertThat(additionalInfo.get(DataConstants.GATEWAY_PARAMETER).asBoolean()).isTrue();
DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
assertThat(credentialsType).isEqualTo(expectedDeviceCredentials.getCredentialsType().name());
assertThat(credentialsValue).isEqualTo(expectedDeviceCredentials.getCredentialsId());
assertThat(status).isEqualTo("SUCCESS");
updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED);
}
@Test
public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception {

2
ui-ngx/src/app/core/http/ota-package.service.ts

@ -59,7 +59,7 @@ export class OtaPackageService {
}
public getOtaPackage(otaPackageId: string, config?: RequestConfig): Observable<OtaPackage> {
return this.http.get<OtaPackage>(`/api/otaPackages/${otaPackageId}`, defaultHttpOptionsFromConfig(config));
return this.http.get<OtaPackage>(`/api/otaPackage/${otaPackageId}`, defaultHttpOptionsFromConfig(config));
}
public getOtaPackageInfo(otaPackageId: string, config?: RequestConfig): Observable<OtaPackageInfo> {

72
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<InputBasicConfig, OutputBasicConfig>
implements ControlValueAccessor, Validator, OnDestroy {
@Input() generalTabContent: TemplateRef<any>;
basicFormGroup: FormGroup;
protected fb = inject(FormBuilder);
protected onChange!: (value: OutputBasicConfig) => void;
protected onTouched!: () => void;
protected destroy$ = new Subject<void>();
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;
}

61
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<BasicConfig> {
gatewayVersion: number;
configVersion: number;
protected constructor(protected gatewayVersionIn: string | number, protected connector: GatewayConnector<BasicConfig>) {
this.gatewayVersion = this.parseVersion(this.gatewayVersionIn);
this.configVersion = this.parseVersion(connector.configVersion);
}
getProcessedByVersion(): GatewayConnector<BasicConfig> {
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<BasicConfig>;
protected abstract getUpgradedVersion(): GatewayConnector<BasicConfig>;
}

68
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<any> {
constructor(
protected gatewayVersionIn: string,
protected connector: GatewayConnector<ModbusBasicConfig>
) {
super(gatewayVersionIn, connector);
}
getUpgradedVersion(): GatewayConnector<ModbusBasicConfig_v3_5_2> {
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<ModbusBasicConfig_v3_5_2>;
}
getDowngradedVersion(): GatewayConnector<ModbusLegacyBasicConfig> {
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<ModbusLegacyBasicConfig>;
}
}

101
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<MQTTBasicConfig> {
private readonly mqttRequestTypeKeys = Object.values(RequestType);
constructor(
protected gatewayVersionIn: string,
protected connector: GatewayConnector<MQTTBasicConfig>
) {
super(gatewayVersionIn, connector);
}
getUpgradedVersion(): GatewayConnector<MQTTBasicConfig_v3_5_2> {
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<MQTTBasicConfig_v3_5_2>;
}
getDowngradedVersion(): GatewayConnector<MQTTLegacyBasicConfig> {
const { requestsMapping, mapping, ...restConfig } = this.connector.configurationJson as MQTTBasicConfig_v3_5_2;
const updatedRequestsMapping =
MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record<RequestType, RequestMappingData[]>);
const updatedMapping = MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping);
return {
...this.connector,
configurationJson: {
...restConfig,
...updatedRequestsMapping,
mapping: updatedMapping,
},
configVersion: this.gatewayVersionIn
} as GatewayConnector<MQTTLegacyBasicConfig>;
}
private cleanUpConfigJson(configurationJson: MQTTBasicConfig_v3_5_2): void {
if (isEqual(configurationJson.requestsMapping, {})) {
delete configurationJson.requestsMapping;
}
if (isEqual(configurationJson.mapping, [])) {
delete configurationJson.mapping;
}
}
}

56
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<OPCBasicConfig> {
constructor(
protected gatewayVersionIn: string,
protected connector: GatewayConnector<OPCBasicConfig>
) {
super(gatewayVersionIn, connector);
}
getUpgradedVersion(): GatewayConnector<OPCBasicConfig_v3_5_2> {
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<OPCBasicConfig_v3_5_2>;
}
getDowngradedVersion(): GatewayConnector<OPCLegacyBasicConfig> {
return {
...this.connector,
configurationJson: {
server: OpcVersionMappingUtil.mapServerToDowngradedVersion(this.connector.configurationJson as OPCBasicConfig_v3_5_2)
},
configVersion: this.gatewayVersionIn
} as GatewayConnector<OPCLegacyBasicConfig>;
}
}

4
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';

6
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<SourceTypes | OPCUaSourceTypes> = Object.values(SourceTypes);
sourceTypes: Array<SourceType | OPCUaSourceType> = Object.values(SourceType);
deviceInfoTypeValue: any;

4
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<MappingValueType | OPCUaSourceTypes> = Object.values(MappingValueType);
valueTypeKeys: Array<MappingValueType | OPCUaSourceType> = Object.values(MappingValueType);
@Input()
valueTypeEnum = MappingValueType;

23
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:

81
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<BasicConfig>
extends GatewayConnectorBasicConfigDirective<ModbusBasicConfig_v3_5_2, BasicConfig> {
enableSlaveControl: FormControl<boolean> = 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, {}));
}
}

110
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<ModbusBasicConfig_v3_5_2> {
export class ModbusBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy {
@Input() generalTabContent: TemplateRef<any>;
basicFormGroup: FormGroup;
enableSlaveControl: FormControl<boolean>;
onChange: (value: ModbusBasicConfig) => void;
onTouched: () => void;
private destroy$ = new Subject<void>();
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,
};
}
}

76
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<ModbusLegacyBasicConfig> {
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,
};
}
}

191
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts

@ -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<any>;
mappingTypes = MappingType;
basicFormGroup: FormGroup;
private onChange: (value: MQTTBasicConfig) => void;
private onTouched: () => void;
private destroy$ = new Subject<void>();
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<RequestType, RequestMappingData[]>): 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<RequestType, RequestMappingData[]> {
return array.reduce((result, { requestType, requestValue }) => {
result[requestType].push(requestValue);
return result;
}, {
connectRequests: [],
disconnectRequests: [],
attributeRequests: [],
attributeUpdates: [],
serverSideRpc: [],
});
}
}

82
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<BasicConfig>
extends GatewayConnectorBasicConfigDirective<MQTTBasicConfig_v3_5_2, BasicConfig> {
MappingType = MappingType;
protected override initBasicFormGroup(): FormGroup {
return this.fb.group({
mapping: [],
requestsMapping: [],
broker: [],
workers: [],
});
}
protected getRequestDataArray(value: Record<RequestType, RequestMappingData[]>): 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<RequestType, RequestMappingValue[]> {
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;
}

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.html → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html

@ -24,12 +24,12 @@
</mat-tab>
<mat-tab label="{{ 'gateway.data-mapping' | translate }}*">
<div class="tb-form-panel no-border no-padding padding-top tb-flex fill-height">
<tb-mapping-table formControlName="dataMapping" [required]="true" [mappingType]="mappingTypes.DATA"></tb-mapping-table>
<tb-mapping-table formControlName="mapping" [required]="true" [mappingType]="MappingType.DATA"></tb-mapping-table>
</div>
</mat-tab>
<mat-tab label="{{ 'gateway.requests-mapping' | translate }}">
<div class="tb-form-panel no-border no-padding padding-top tb-flex fill-height">
<tb-mapping-table formControlName="requestsMapping" [mappingType]="mappingTypes.REQUESTS"></tb-mapping-table>
<tb-mapping-table formControlName="requestsMapping" [mappingType]="MappingType.REQUESTS"></tb-mapping-table>
</div>
</mat-tab>
<mat-tab label="{{ 'gateway.workers-settings' | translate }}">

0
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss

103
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<MQTTBasicConfig_v3_5_2> {
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<RequestType, RequestMappingData[]>),
};
}
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 };
}
}

124
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<MQTTLegacyBasicConfig> {
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<RequestType, RequestMappingData[]>))
};
}
}

0
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts → 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',

0
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.html → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.html

0
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component.ts

134
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts

@ -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<any>;
mappingTypes = MappingType;
basicFormGroup: FormGroup;
onChange!: (value: string) => void;
onTouched!: () => void;
protected readonly connectorType = ConnectorType;
private destroy$ = new Subject<void>();
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}
};
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html → 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 @@
</mat-form-field>
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div *ngIf="!hideNewFields" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ 'gateway.hints.poll-period' | translate }}">
<div tbTruncateWithTooltip>{{ 'gateway.poll-period' | translate }}</div>
</div>

0
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component.scss

15
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts → 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();

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html → 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 @@
<ng-container [ngTemplateOutlet]="generalTabContent"></ng-container>
</mat-tab>
<mat-tab label="{{ 'gateway.server' | translate }}*">
<tb-opc-server-config formControlName="server"></tb-opc-server-config>
<tb-opc-server-config formControlName="server" [hideNewFields]="isLegacy"></tb-opc-server-config>
</mat-tab>
<mat-tab label="{{ 'gateway.data-mapping' | translate }}*">
<div class="tb-form-panel no-border no-padding padding-top tb-flex fill-height">

0
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc/opc-ua-basic-config/opc-ua-basic-config.component.scss

88
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<OPCBasicConfig_v3_5_2, OPCBasicConfig_v3_5_2> {
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,
};
}
}

88
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<OPCBasicConfig_v3_5_2, OPCLegacyBasicConfig> {
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),
};
}
}

24
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<AddConnectorDialogComponent, BaseData<HasId>> implements OnInit, OnDestroy {
export class AddConnectorDialogComponent
extends DialogComponent<AddConnectorDialogComponent, BaseData<HasId>> implements OnInit, OnDestroy {
connectorForm: UntypedFormGroup;
@ -95,8 +98,15 @@ export class AddConnectorDialogComponent extends DialogComponent<AddConnectorDia
this.submitted = true;
const value = this.connectorForm.getRawValue();
if (value.useDefaults) {
getDefaultConfig(this.resourcesService, value.type).subscribe((defaultConfig) => {
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<AddConnectorDia
takeUntil(this.destroy$),
).subscribe();
}
private getDefaultConfig(type: string): Observable<GatewayVersionedDefaultConfig | GatewayConnector> {
return this.resourcesService.loadJsonResource(`/assets/metadata/connector-default-configs/${type}.json`);
};
}

27
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<MappingDialogCompone
ConvertorTypeEnum = ConvertorType;
ConvertorTypeTranslationsMap = ConvertorTypeTranslationsMap;
sourceTypes: SourceTypes[] = Object.values(SourceTypes);
OPCUaSourceTypes = Object.values(OPCUaSourceTypes) as Array<OPCUaSourceTypes>;
OPCUaSourceTypesEnum = OPCUaSourceTypes;
sourceTypesEnum = SourceTypes;
sourceTypes: SourceType[] = Object.values(SourceType);
OPCUaSourceTypes = Object.values(OPCUaSourceType) as Array<OPCUaSourceType>;
OPCUaSourceTypesEnum = OPCUaSourceType;
sourceTypesEnum = SourceType;
SourceTypeTranslationsMap = SourceTypeTranslationsMap;
requestTypes: RequestType[] = Object.values(RequestType);
@ -230,8 +231,8 @@ export class MappingDialogComponent extends DialogComponent<MappingDialogCompone
noKeysText: MappingKeysNoKeysTextTranslationsMap.get(keysType)
};
if (this.data.mappingType === MappingType.OPCUA) {
ctx.valueTypeKeys = Object.values(OPCUaSourceTypes);
ctx.valueTypeEnum = OPCUaSourceTypes;
ctx.valueTypeKeys = Object.values(OPCUaSourceType);
ctx.valueTypeEnum = OPCUaSourceType;
ctx.valueTypes = SourceTypeTranslationsMap;
}
this.keysPopupClosed = false;
@ -293,8 +294,8 @@ export class MappingDialogComponent extends DialogComponent<MappingDialogCompone
requestType: this.data.value.requestType,
requestValue: {
[this.data.value.requestType]: this.data.value.requestValue
}
} as RequestMappingFormValue;
} as Record<RequestType, RequestMappingData>
};
default:
return this.data.value as DeviceConnectorMapping;
}
@ -349,10 +350,10 @@ export class MappingDialogComponent extends DialogComponent<MappingDialogCompone
attributeRequests: this.fb.group({
topicFilter: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
deviceInfo: this.fb.group({
deviceNameExpressionSource: [SourceTypes.MSG, []],
deviceNameExpressionSource: [SourceType.MSG, []],
deviceNameExpression: ['', [Validators.required]],
}),
attributeNameExpressionSource: [SourceTypes.MSG, []],
attributeNameExpressionSource: [SourceType.MSG, []],
attributeNameExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
topicExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
valueExpression: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
@ -407,7 +408,7 @@ export class MappingDialogComponent extends DialogComponent<MappingDialogCompone
private createOPCUAMappingForm(): void {
this.mappingForm = this.fb.group({
deviceNodeSource: [OPCUaSourceTypes.PATH, []],
deviceNodeSource: [OPCUaSourceType.PATH, []],
deviceNodePattern: ['', [Validators.required]],
deviceInfo: [{}, []],
attributes: [[], []],

43
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html

@ -156,6 +156,7 @@
<div class="tb-form-panel-title">
{{ initialConnector?.type ? GatewayConnectorTypesTranslatesMap.get(initialConnector.type) : '' }}
{{ 'gateway.configuration' | translate }}
<span class="version-placeholder" *ngIf="connectorForm.get('configVersion').value">v{{connectorForm.get('configVersion').value}}</span>
</div>
<tb-toggle-select *ngIf="initialConnector && allowBasicConfig.has(initialConnector.type)"
formControlName="mode" appearance="fill">
@ -175,18 +176,36 @@
<section class="tb-form-panel section-container no-border no-padding tb-flex space-between" *ngIf="initialConnector">
<ng-container *ngIf="connectorForm.get('mode')?.value === ConnectorConfigurationModes.BASIC else defaultConfig">
<ng-container [ngSwitch]="initialConnector.type">
<tb-mqtt-basic-config *ngSwitchCase="ConnectorType.MQTT"
formControlName="basicConfig"
[generalTabContent]="generalTabContent">
</tb-mqtt-basic-config>
<tb-opc-ua-basic-config *ngSwitchCase="ConnectorType.OPCUA"
formControlName="basicConfig"
[generalTabContent]="generalTabContent">
</tb-opc-ua-basic-config>
<tb-modbus-basic-config *ngSwitchCase="ConnectorType.MODBUS"
formControlName="basicConfig"
[generalTabContent]="generalTabContent">
</tb-modbus-basic-config>
<ng-container *ngSwitchCase="ConnectorType.MQTT">
<tb-mqtt-basic-config
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
formControlName="basicConfig"
[generalTabContent]="generalTabContent"
/>
<ng-template #legacy>
<tb-mqtt-legacy-basic-config formControlName="basicConfig" [generalTabContent]="generalTabContent"/>
</ng-template>
</ng-container>
<ng-container *ngSwitchCase="ConnectorType.OPCUA">
<tb-opc-ua-basic-config
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
formControlName="basicConfig"
[generalTabContent]="generalTabContent"
/>
<ng-template #legacy>
<tb-opc-ua-legacy-basic-config formControlName="basicConfig" [generalTabContent]="generalTabContent"/>
</ng-template>
</ng-container>
<ng-container *ngSwitchCase="ConnectorType.MODBUS">
<tb-modbus-basic-config
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
formControlName="basicConfig"
[generalTabContent]="generalTabContent"
/>
<ng-template #legacy>
<tb-modbus-legacy-basic-config formControlName="basicConfig" [generalTabContent]="generalTabContent"/>
</ng-template>
</ng-container>
</ng-container>
</ng-container>
<ng-template #defaultConfig>

5
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%;

32
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<GatewayAttributeData>;
@ -106,6 +111,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
mode: ConfigurationModes = this.ConnectorConfigurationModes.BASIC;
initialConnector: GatewayConnector;
private gatewayVersion: string;
private inactiveConnectors: Array<string>;
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, AddConnectorConfigData>(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();

288
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<BaseConfig = ConnectorBaseConfig> extends GatewayConnectorBase {
configurationJson: BaseConfig;
basicConfig?: BaseConfig;
}
export interface GatewayVersionedDefaultConfig {
legacy: GatewayConnector<ConnectorLegacyConfig>;
'3.5.2': GatewayConnector<ConnectorBaseConfig_v3_5_2>;
}
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<RequestType, RequestMappingData[]> | RequestMappingData[];
export type MQTTBasicConfig = MQTTBasicConfig_v3_5_2 | MQTTLegacyBasicConfig;
export interface MQTTBasicConfig_v3_5_2 {
mapping: ConverterConnectorMapping[];
requestsMapping: Record<RequestType, RequestMappingData[] | RequestMappingValue[]> | 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<ServerConfig, 'enableSubscriptions'> {
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<string, number>;
}
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<string, unknown>;
}
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<ConverterConnectorMapping, 'converter'> & {
converter: {
type: ConvertorType;
@ -275,14 +363,24 @@ export type ConverterMappingFormValue = Omit<ConverterConnectorMapping, 'convert
export interface DeviceConnectorMapping {
deviceNodePattern: string;
deviceNodeSource: string;
deviceInfo: DeviceInfo;
deviceNodeSource: OPCUaSourceType;
deviceInfo: ConnectorDeviceInfo;
attributes?: Attribute[];
timeseries?: Timeseries[];
rpc_methods?: RpcMethod[];
attributes_updates?: AttributesUpdate[];
}
export interface LegacyDeviceConnectorMapping {
deviceNamePattern: string;
deviceNodePattern: string;
deviceTypePattern: string;
attributes?: LegacyAttribute[];
timeseries?: LegacyTimeseries[];
rpc_methods?: LegacyRpcMethod[];
attributes_updates?: LegacyDeviceAttributeUpdate[];
}
export enum ConnectorType {
MQTT = 'mqtt',
MODBUS = 'modbus',
@ -461,6 +559,7 @@ export interface GatewayLogData {
export interface AddConnectorConfigData {
dataSourceData: Array<any>;
gatewayVersion: string;
}
export interface CreatedConnectorConfigData {
@ -591,13 +690,13 @@ export const ConvertorTypeTranslationsMap = new Map<ConvertorType, string>(
]
);
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<SourceTypes | OPCUaSourceTypes, string>(
export const SourceTypeTranslationsMap = new Map<SourceType | OPCUaSourceType, string>(
[
[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<RequestMappingData, 'requestValue'> & {
requestValue: Record<RequestType, RequestDataItem>;
};
export interface RequestDataItem {
type: string;
details: string;
export interface RequestMappingFormValue {
requestType: RequestType;
methodFilter?: string;
attributeFilter?: string;
topicFilter?: string;
requestValue: Record<RequestType, RequestMappingData>;
}
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<any> =>
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<ModbusSlave, 'values'> {
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;

10
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<SourceTypes | OPCUaSourceTypes | MappingValueType> ): string {
if (!sourceTypes || sourceTypes?.includes(OPCUaSourceTypes.PATH)) {
if (sourceType !== OPCUaSourceTypes.CONST) {
transform(field: string, sourceType: SourceType | OPCUaSourceType, sourceTypes?: Array<SourceType | OPCUaSourceType | MappingValueType> ): string {
if (!sourceTypes || sourceTypes?.includes(OPCUaSourceType.PATH)) {
if (sourceType !== OPCUaSourceType.CONST) {
return `widget/lib/gateway/${field}-${sourceType}_fn`;
} else {
return;

42
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<MQTTBasicConfig>).getProcessedByVersion();
case ConnectorType.OPCUA:
return new OpcVersionProcessor(gatewayVersion, connector as GatewayConnector<OPCBasicConfig>).getProcessedByVersion();
case ConnectorType.MODBUS:
return new ModbusVersionProcessor(gatewayVersion, connector as GatewayConnector<ModbusBasicConfig>).getProcessedByVersion();
default:
return connector;
}
}
}

80
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);
}
}

217
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<RequestType,
LegacyRequestMappingData[]>
): Record<RequestType, RequestMappingData[]> {
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<RequestType, RequestMappingData[]>;
}
static mapRequestsToDowngradedVersion(
requestsMapping: Record<RequestType, RequestMappingData[]>
): Record<RequestType, LegacyRequestMappingData[]> {
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<RequestType, LegacyRequestMappingData[]>;
}
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<string, unknown>): void {
this.mqttRequestMappingOldFields.forEach(field => delete obj[field]);
deleteNullProperties(obj);
}
private static cleanUpNewFields(obj: Record<string, unknown>): 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
};
}
}

134
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';
}
}
}

22
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,

11
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<EntityTableC
const authUser = getCurrentAuthUser(this.store);
this.config.deleteEnabled = (resource) => 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<EntityTableC
return false;
}
private detailsReadonly(resource: ResourceInfo, authority: Authority): boolean {
if (resource?.resourceType === ResourceType.LWM2M_MODEL) {
return true;
}
return !this.isResourceEditable(resource, authority);
}
private isResourceEditable(resource: ResourceInfo, authority: Authority): boolean {
if (authority === Authority.TENANT_ADMIN) {
return resource && resource.tenantId && resource.tenantId.id !== NULL_UUID;

2
ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html

@ -58,7 +58,7 @@
</mat-form-field>
<mat-form-field class="mat-block" *ngIf="entityForm.get('resourceType').value !== resourceType.LWM2M_MODEL || !isAdd">
<mat-label translate>resource.title</mat-label>
<input matInput formControlName="title" required [readonly]="entityForm.get('resourceType').value === resourceType.LWM2M_MODEL">
<input matInput formControlName="title" required>
<mat-error *ngIf="entityForm.get('title').hasError('required')">
{{ 'resource.title-required' | translate }}
</mat-error>

36
ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts

@ -43,8 +43,7 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> 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<void>();
@ -57,20 +56,20 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> 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<Resource> 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<Resource> 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<Resource> impleme
}
}
getAcceptType() {
getAcceptType(): string {
try {
return ResourceTypeMIMETypes.get(this.entityForm.get('resourceType').value);
} catch (e) {
@ -129,7 +127,7 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
return window.btoa(data);
}
onResourceIdCopied() {
onResourceIdCopied(): void {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('resource.idCopiedMessage'),

2
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`,

698
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": []
}
]
}
}
}
}
}

595
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}"
}
]
}
}

180
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"
}
]
}
]
}
}
}

Loading…
Cancel
Save