diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index 28a9e1ff4b..694fed1bf8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.MqttQoS; +import io.netty.handler.codec.mqtt.MqttVersion; import io.netty.handler.ssl.SslContext; import io.netty.util.concurrent.Promise; import lombok.extern.slf4j.Slf4j; @@ -54,7 +55,7 @@ import java.util.concurrent.TimeoutException; type = ComponentType.EXTERNAL, name = "mqtt", configClazz = TbMqttNodeConfiguration.class, - version = 1, + version = 2, clusteringMode = ComponentClusteringMode.USER_PREFERENCE, nodeDescription = "Publish messages to the MQTT broker", nodeDetails = "Will publish message payload to the MQTT broker with QoS AT_LEAST_ONCE.", @@ -126,6 +127,7 @@ public class TbMqttNode extends TbAbstractExternalNode { config.setClientId(getClientId(ctx)); } config.setCleanSession(this.mqttNodeConfiguration.isCleanSession()); + config.setProtocolVersion(this.mqttNodeConfiguration.getProtocolVersion()); MqttClientSettings mqttClientSettings = ctx.getMqttClientSettings(); config.setRetransmissionConfig(new MqttClientConfig.RetransmissionConfig( @@ -201,10 +203,17 @@ public class TbMqttNode extends TbAbstractExternalNode { hasChanges = true; ((ObjectNode) oldConfiguration).put(parseToPlainText, false); } + case 1: + String protocolVersion = "protocolVersion"; + if (!oldConfiguration.has(protocolVersion)) { + hasChanges = true; + ((ObjectNode) oldConfiguration).put(protocolVersion, MqttVersion.MQTT_3_1.name()); + } break; default: break; } return new TbPair<>(hasChanges, oldConfiguration); } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java index faa465193a..8bbe920277 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.mqtt; +import io.netty.handler.codec.mqtt.MqttVersion; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.credentials.AnonymousCredentials; @@ -30,10 +31,10 @@ public class TbMqttNodeConfiguration implements NodeConfiguration upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + boolean hasChanges = false; + switch (fromVersion) { + case 0: + String protocolVersion = "protocolVersion"; + if (!oldConfiguration.has(protocolVersion)) { + hasChanges = true; + ((ObjectNode) oldConfiguration).put(protocolVersion, MqttVersion.MQTT_3_1_1.name()); + } + break; + default: + break; + } + return new TbPair<>(hasChanges, oldConfiguration); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeConfiguration.java index 2c48c193f0..64d839dc52 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeConfiguration.java @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.mqtt.azure; +import io.netty.handler.codec.mqtt.MqttVersion; import lombok.Data; import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration; @@ -30,6 +31,7 @@ public class TbAzureIotHubNodeConfiguration extends TbMqttNodeConfiguration { configuration.setConnectTimeoutSec(10); configuration.setCleanSession(true); configuration.setSsl(true); + configuration.setProtocolVersion(MqttVersion.MQTT_3_1_1); configuration.setCredentials(new AzureIotHubSasCredentials()); return configuration; } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/AbstractRuleNodeUpgradeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/AbstractRuleNodeUpgradeTest.java index 6554c7da01..60cb339bcc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/AbstractRuleNodeUpgradeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/AbstractRuleNodeUpgradeTest.java @@ -49,4 +49,5 @@ public abstract class AbstractRuleNodeUpgradeTest { ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); assertThat(upgradedConfig).isEqualTo(expectedConfig); } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeTest.java index 2438e59106..1e877b80dc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeTest.java @@ -20,6 +20,7 @@ import io.netty.buffer.Unpooled; import io.netty.channel.EventLoopGroup; import io.netty.handler.codec.mqtt.MqttConnectReturnCode; import io.netty.handler.codec.mqtt.MqttQoS; +import io.netty.handler.codec.mqtt.MqttVersion; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.util.concurrent.Future; @@ -138,6 +139,7 @@ public class TbMqttNodeTest extends AbstractRuleNodeUpgradeTest { assertThat(mqttNodeConfig.isCleanSession()).isTrue(); assertThat(mqttNodeConfig.isSsl()).isFalse(); assertThat(mqttNodeConfig.isParseToPlainText()).isFalse(); + assertThat(mqttNodeConfig.getProtocolVersion()).isEqualTo(MqttVersion.MQTT_3_1_1); assertThat(mqttNodeConfig.getCredentials()).isInstanceOf(AnonymousCredentials.class); } @@ -382,20 +384,42 @@ public class TbMqttNodeTest extends AbstractRuleNodeUpgradeTest { then(mqttClientMock).shouldHaveNoInteractions(); } + @ParameterizedTest + @MethodSource + public void verifyProtocolVersionMapping(MqttVersion expectedVersion) throws Exception { + mqttNodeConfig.setProtocolVersion(expectedVersion); + + given(ctxMock.isExternalNodeForceAck()).willReturn(false); + mockSuccessfulInit(); + mqttNode.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(mqttNodeConfig))); + + ArgumentCaptor configCaptor = ArgumentCaptor.forClass(MqttClientConfig.class); + then(mqttNode).should().prepareMqttClientConfig(configCaptor.capture()); + assertThat(expectedVersion).isEqualTo(configCaptor.getValue().getProtocolVersion()); + } + + private static Stream verifyProtocolVersionMapping() { + return Stream.of(MqttVersion.values()).map(Arguments::of); + } + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { return Stream.of( // default config for version 0 Arguments.of(0, "{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"}}", true, - "{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false}"), + "{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false, \"protocolVersion\":\"MQTT_3_1\"}"), // default config for version 1 with upgrade from version 0 Arguments.of(1, "{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false}", + true, + "{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false, \"protocolVersion\":\"MQTT_3_1\"}"), + // default config for version 2 with upgrade from version 1 + Arguments.of(2, + "{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false, \"protocolVersion\":\"MQTT_3_1\"}", false, - "{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false}") + "{\"topicPattern\":\"my-topic\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"anonymous\"},\"parseToPlainText\":false, \"protocolVersion\":\"MQTT_3_1\"}") ); - } @Override diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeTest.java index 441d75b2d4..433d5d4673 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNodeTest.java @@ -19,6 +19,7 @@ import io.netty.handler.codec.mqtt.MqttVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.provider.Arguments; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; @@ -26,11 +27,15 @@ import org.thingsboard.common.util.AzureIotHubUtil; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; +import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest; import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.credentials.CertPemCredentials; import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration; +import java.util.stream.Stream; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; @@ -38,7 +43,7 @@ import static org.mockito.BDDMockito.spy; import static org.mockito.BDDMockito.willReturn; @ExtendWith(MockitoExtension.class) -public class TbAzureIotHubNodeTest { +public class TbAzureIotHubNodeTest extends AbstractRuleNodeUpgradeTest { private TbAzureIotHubNode azureIotHubNode; private TbAzureIotHubNodeConfiguration azureIotHubNodeConfig; @@ -66,6 +71,7 @@ public class TbAzureIotHubNodeTest { assertThat(azureIotHubNodeConfig.isCleanSession()).isTrue(); assertThat(azureIotHubNodeConfig.isSsl()).isTrue(); assertThat(azureIotHubNodeConfig.isParseToPlainText()).isFalse(); + assertThat(azureIotHubNodeConfig.getProtocolVersion()).isEqualTo(MqttVersion.MQTT_3_1_1); assertThat(azureIotHubNodeConfig.getCredentials()).isInstanceOf(AzureIotHubSasCredentials.class); } @@ -82,7 +88,6 @@ public class TbAzureIotHubNodeTest { MqttClientConfig mqttClientConfig = new MqttClientConfig(); azureIotHubNode.prepareMqttClientConfig(mqttClientConfig); - assertThat(mqttClientConfig.getProtocolVersion()).isEqualTo(MqttVersion.MQTT_3_1_1); assertThat(mqttClientConfig.getUsername()).isEqualTo(AzureIotHubUtil.buildUsername(azureIotHubNodeConfig.getHost(), mqttClientConfig.getClientId())); assertThat(mqttClientConfig.getPassword()).isEqualTo(AzureIotHubUtil.buildSasToken(azureIotHubNodeConfig.getHost(), credentials.getSasKey())); } @@ -105,4 +110,24 @@ public class TbAzureIotHubNodeTest { assertThat(mqttNodeConfiguration.isCleanSession()).isTrue(); } + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { + return Stream.of( + // default config for version 0 + Arguments.of(0, + "{\"topicPattern\":\"devices//messages/events/\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"sas\",\"sasKey\":\"sasKey\",\"caCert\":null,\"caCertFileName\":null}}}", + true, + "{\"topicPattern\":\"devices//messages/events/\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"sas\",\"sasKey\":\"sasKey\",\"caCert\":null,\"caCertFileName\":null}, \"protocolVersion\":\"MQTT_3_1_1\"}\"}"), + // default config for version 1 with upgrade from version 0 + Arguments.of(1, + "{\"topicPattern\":\"devices//messages/events/\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"sas\",\"sasKey\":\"sasKey\",\"caCert\":null,\"caCertFileName\":null}, \"protocolVersion\":\"MQTT_3_1_1\"}\"}", + false, + "{\"topicPattern\":\"devices//messages/events/\",\"port\":1883,\"connectTimeoutSec\":10,\"cleanSession\":true, \"ssl\":false, \"retainedMessage\":false,\"credentials\":{\"type\":\"sas\",\"sasKey\":\"sasKey\",\"caCert\":null,\"caCertFileName\":null}, \"protocolVersion\":\"MQTT_3_1_1\"}\"}") + ); + } + + @Override + protected TbNode getTestNode() { + return azureIotHubNode; + } + } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html index ee9c46d8c1..79c4a2f00d 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html @@ -38,6 +38,7 @@ {{ 'rule-node-config.device-id-required' | translate }} + diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.ts index 7c96131fef..7dd934ccd9 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.ts @@ -53,6 +53,7 @@ export class AzureIotHubConfigComponent extends RuleNodeConfigurationComponent { clientId: [configuration ? configuration.clientId : null, [Validators.required]], cleanSession: [configuration ? configuration.cleanSession : false, []], ssl: [configuration ? configuration.ssl : false, []], + protocolVersion: [configuration ? configuration.protocolVersion : null, []], credentials: this.fb.group( { type: [configuration && configuration.credentials ? configuration.credentials.type : null, [Validators.required]], diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html index 090fea41f3..60adadd6bb 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html @@ -72,6 +72,7 @@ {{ 'rule-node-config.parse-to-plain-text' | translate }}
{{ "rule-node-config.parse-to-plain-text-hint" | translate }}
+ {{ 'rule-node-config.clean-session' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.ts index 00a95e5ce6..08c8e284a9 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.ts @@ -52,6 +52,7 @@ export class MqttConfigComponent extends RuleNodeConfigurationComponent { cleanSession: [configuration ? configuration.cleanSession : false, []], retainedMessage: [configuration ? configuration.retainedMessage : false, []], ssl: [configuration ? configuration.ssl : false, []], + protocolVersion: [configuration ? configuration.protocolVersion : null, []], credentials: [configuration ? configuration.credentials : null, []] }); } diff --git a/ui-ngx/src/app/shared/components/mqtt-version-select.component.html b/ui-ngx/src/app/shared/components/mqtt-version-select.component.html new file mode 100644 index 0000000000..81029a3445 --- /dev/null +++ b/ui-ngx/src/app/shared/components/mqtt-version-select.component.html @@ -0,0 +1,30 @@ + + + device-profile.mqtt-protocol-version + + @for (version of mqttVersions; track version) { + + {{ mqttVersionTranslation.get(version) }} + + } + + diff --git a/ui-ngx/src/app/shared/components/mqtt-version-select.component.ts b/ui-ngx/src/app/shared/components/mqtt-version-select.component.ts new file mode 100644 index 0000000000..6bde856a7b --- /dev/null +++ b/ui-ngx/src/app/shared/components/mqtt-version-select.component.ts @@ -0,0 +1,79 @@ +/// +/// Copyright © 2016-2025 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, Input } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { SubscriptSizing, MatFormFieldAppearance } from '@angular/material/form-field'; +import { MqttVersionTranslation, MqttVersion } from '@shared/models/mqtt.models'; + +@Component({ + selector: 'tb-mqtt-version-select', + templateUrl: './mqtt-version-select.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MqttVersionSelectComponent), + multi: true + }] +}) +export class MqttVersionSelectComponent implements ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + subscriptSizing: SubscriptSizing = 'dynamic'; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + + mqttVersions = Object.values(MqttVersion); + mqttVersionTranslation = MqttVersionTranslation; + modelValue: MqttVersion; + + @Input() + @coerceBoolean() + required = false; + + private propagateChange = (v: any) => { }; + + constructor() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: MqttVersion | null): void { + this.modelValue = value; + } + + mqttVersionChanged() { + this.updateView(); + } + + private updateView() { + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/shared/models/mqtt.models.ts b/ui-ngx/src/app/shared/models/mqtt.models.ts new file mode 100644 index 0000000000..2bd40f3681 --- /dev/null +++ b/ui-ngx/src/app/shared/models/mqtt.models.ts @@ -0,0 +1,29 @@ +/// +/// Copyright © 2016-2025 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. +/// + +export enum MqttVersion { + MQTT_3_1 = 'MQTT_3_1', + MQTT_3_1_1 = 'MQTT_3_1_1', + MQTT_5 = 'MQTT_5' +} + +export const DEFAULT_MQTT_VERSION = MqttVersion.MQTT_3_1_1; + +export const MqttVersionTranslation = new Map([ + [MqttVersion.MQTT_3_1, 'MQTT 3.1'], + [MqttVersion.MQTT_3_1_1, 'MQTT 3.1.1'], + [MqttVersion.MQTT_5, 'MQTT 5.0'] +]); diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 47a65631db..ec7219d78a 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -227,6 +227,7 @@ import { JsFuncModulesComponent } from '@shared/components/js-func-modules.compo import { JsFuncModuleRowComponent } from '@shared/components/js-func-module-row.component'; import { EntityKeyAutocompleteComponent } from '@shared/components/entity/entity-key-autocomplete.component'; import { DurationLeftPipe } from '@shared/pipe/duration-left.pipe'; +import { MqttVersionSelectComponent } from '@shared/components/mqtt-version-select.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -439,6 +440,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) HexInputComponent, ScadaSymbolInputComponent, EntityKeyAutocompleteComponent, + MqttVersionSelectComponent, ], imports: [ CommonModule, @@ -702,6 +704,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) WidgetButtonComponent, ScadaSymbolInputComponent, EntityKeyAutocompleteComponent, + MqttVersionSelectComponent, ] }) export class SharedModule { } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e02095d6c3..cdc8fac5fe 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1919,6 +1919,7 @@ "mqtt-use-json-format-for-default-downlink-topics-hint": "When enabled, the platform will use Json payload format to push attributes and RPC via the following topics: v1/devices/me/attributes/response/$request_id, v1/devices/me/attributes, v1/devices/me/rpc/request/$request_id, v1/devices/me/rpc/response/$request_id. This setting does not impact attribute and rpc subscriptions sent using new (v2) topics: v2/a/res/$request_id, v2/a, v2/r/req/$request_id, v2/r/res/$request_id. Where $request_id is an integer request identifier.", "mqtt-send-ack-on-validation-exception": "Send PUBACK on PUBLISH message validation failure", "mqtt-send-ack-on-validation-exception-hint": "By default, the platform will close the MQTT session on message validation failure. When enabled, the platform will send publish acknowledgment instead of closing the session.", + "mqtt-protocol-version": "Protocol version", "snmp-add-mapping": "Add SNMP mapping", "snmp-mapping-not-configured": "No mapping for OID to time series/telemetry configured", "snmp-timseries-or-attribute-name": "Time series/attribute name for mapping",