Browse Source

Added workaround for version compatibility of Gateway MQTT Connectors

pull/11516/head
mpetrov 2 years ago
parent
commit
adf8ec31c5
  1. 6
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/device-info-table/device-info-table.component.ts
  2. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-data-keys-panel/mapping-data-keys-panel.component.ts
  3. 23
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts
  4. 191
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts
  5. 107
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract.ts
  6. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.html
  7. 0
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.scss
  8. 107
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.component.ts
  9. 128
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-legacy-basic-config.component.ts
  10. 0
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.html
  11. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component.ts
  12. 8
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts
  13. 16
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts
  14. 27
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/mapping-dialog.component.ts
  15. 15
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html
  16. 5
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss
  17. 27
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  18. 215
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  19. 10
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts
  20. 66
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts
  21. 279
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts
  22. 8
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  23. 597
      ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json

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:

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: [],
});
}
}

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

@ -0,0 +1,107 @@
///
/// 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 { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { isObject } from 'lodash';
import {
MappingType,
MQTTBasicConfig,
MQTTLegacyBasicConfig,
RequestMappingData,
RequestMappingValue,
RequestType
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { Directive, OnDestroy } from '@angular/core';
@Directive()
export abstract class AbstractMqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy {
basicFormGroup: FormGroup;
mappingTypes = MappingType;
private destroy$ = new Subject<void>();
private onChange: (value: MQTTBasicConfig | MQTTLegacyBasicConfig) => void;
private onTouched: () => void;
constructor(protected fb: FormBuilder) {
this.basicFormGroup = this.fb.group({
mapping: [],
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 | MQTTLegacyBasicConfig) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
validate(): ValidationErrors | null {
return this.basicFormGroup.valid ? null : {
basicFormGroup: {valid: false}
};
}
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: [],
});
}
abstract writeValue(basicConfig: MQTTBasicConfig | MQTTLegacyBasicConfig): void;
protected abstract getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig | MQTTLegacyBasicConfig;
}

2
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,7 +24,7 @@
</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]="mappingTypes.DATA"></tb-mapping-table>
</div>
</mat-tab>
<mat-tab label="{{ 'gateway.requests-mapping' | 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

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

@ -0,0 +1,107 @@
///
/// 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, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import {
MQTTBasicConfig,
RequestMappingData,
RequestMappingValue,
RequestType
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import {
AbstractMqttBasicConfigComponent
} 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/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 AbstractMqttBasicConfigComponent {
@Input()
generalTabContent: TemplateRef<any>;
writeValue(basicConfig: MQTTBasicConfig): void {
const { broker, mapping = [], requestsMapping } = basicConfig;
const editedBase = {
workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? {
maxNumberOfWorkers: broker.maxNumberOfWorkers,
maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker,
} : {},
mapping: mapping || [],
broker: broker || {},
requestsMapping: this.getRequestDataArray(requestsMapping as Record<RequestType, RequestMappingData[]>),
};
this.basicFormGroup.setValue(editedBase, {emitEvent: false});
}
protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig {
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 };
}
}

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

@ -0,0 +1,128 @@
///
/// 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, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import {
MQTTBasicConfig,
MQTTLegacyBasicConfig,
RequestMappingData,
RequestMappingValue,
RequestType
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util';
import {
AbstractMqttBasicConfigComponent
} 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/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 AbstractMqttBasicConfigComponent {
@Input()
generalTabContent: TemplateRef<any>;
writeValue(basicConfig: any): void {
const {
broker,
mapping = [],
connectRequests = [],
disconnectRequests = [],
attributeRequests = [],
attributeUpdates = [],
serverSideRpc = []
} = basicConfig;
const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToNewestVersion({
connectRequests,
disconnectRequests,
attributeRequests,
attributeUpdates,
serverSideRpc
});
const editedBase = {
workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? {
maxNumberOfWorkers: broker.maxNumberOfWorkers,
maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker,
} : {},
mapping: MqttVersionMappingUtil.mapMappingToNewestVersion(mapping) || [],
broker: broker || {},
requestsMapping: this.getRequestDataArray(updatedRequestMapping),
};
this.basicFormGroup.setValue(editedBase, {emitEvent: false});
}
protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig): 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.mapMappingToLegacyVersion(mapping),
...(MqttVersionMappingUtil.mapRequestsToLegacyVersion(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

@ -37,7 +37,7 @@ import { TranslateService } from '@ngx-translate/core';
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',

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

@ -36,12 +36,6 @@ 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';
@ -70,8 +64,6 @@ import {
CommonModule,
SharedModule,
SecurityConfigComponent,
WorkersConfigControlComponent,
BrokerConfigControlComponent,
MappingTableComponent,
OpcServerConfigComponent,
],

16
ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts

@ -26,12 +26,13 @@ import {
AddConnectorConfigData,
ConnectorType,
CreatedConnectorConfigData,
GatewayConnector,
GatewayConnectorDefaultTypesTranslatesMap,
GatewayLogLevel,
getDefaultConfig,
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 +43,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 +97,8 @@ 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(({current, legacy, ...defaultConfig}: GatewayVersionedDefaultConfig) => {
value.configurationJson = (this.data.gatewayVersion ? current : legacy) ?? defaultConfig;
if (this.connectorForm.valid) {
this.dialogRef.close(value);
}
@ -130,4 +132,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: [[], []],

15
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,10 +176,16 @@
<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>
<ng-container *ngSwitchCase="ConnectorType.MQTT">
<tb-mqtt-basic-config
*ngIf="connectorForm.get('configVersion').value else legacy"
formControlName="basicConfig"
[generalTabContent]="generalTabContent"
/>
<ng-template #legacy>
<tb-mqtt-legacy-basic-config formControlName="basicConfig" [generalTabContent]="generalTabContent"/>
</ng-template>
</ng-container>
<tb-opc-ua-basic-config *ngSwitchCase="ConnectorType.OPCUA"
formControlName="basicConfig"
[generalTabContent]="generalTabContent">

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

27
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts

@ -65,6 +65,7 @@ import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway
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 './utils/gateway-connector-version-mapping.util';
export class ForceErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null): boolean {
@ -106,6 +107,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
mode: ConnectorConfigurationModes = this.ConnectorConfigurationModes.BASIC;
initialConnector: GatewayConnector;
private gatewayVersion: string;
private inactiveConnectors: Array<string>;
private attributeDataSource: AttributeDatasource;
private inactiveConnectorsDataSource: AttributeDatasource;
@ -241,9 +243,13 @@ 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;
return GatewayConnectorVersionMappingUtil.getMappedByVersion(value, this.gatewayVersion);
}
private updateData(reload: boolean = false): void {
@ -265,7 +271,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;
@ -342,7 +348,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
class: '',
configuration: '',
configurationJson: {},
basicConfig: {}
basicConfig: {},
configVersion: ''
}, {emitEvent: false});
this.connectorForm.markAsPristine();
}
@ -475,7 +482,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
value.configurationJson = {} as ConnectorBaseConfig;
}
value.basicConfig = value.configurationJson;
this.updateConnector(value);
this.updateConnector({...value, configVersion: this.gatewayVersion ?? ''});
this.generate('basicConfig.broker.clientId');
setTimeout(() => this.saveConnector());
});
@ -485,7 +492,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();
}
@ -528,7 +538,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
class: [''],
configuration: [''],
configurationJson: [{}, [Validators.required]],
basicConfig: [{}]
basicConfig: [{}],
configVersion: [''],
});
this.connectorForm.disable();
}
@ -571,10 +582,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);
});

215
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';
@ -114,21 +112,37 @@ 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?: ConnectorConfigurationModes;
configVersion?: string;
}
export type GatewayConnector = CurrentGatewayConnector | LegacyGatewayConnector;
export interface CurrentGatewayConnector extends GatewayConnectorBase {
configurationJson: ConnectorBaseConfig;
basicConfig?: ConnectorBaseConfig;
}
export interface LegacyGatewayConnector extends GatewayConnectorBase {
configurationJson: ConnectorLegacyBaseConfig;
basicConfig?: ConnectorLegacyBaseConfig;
}
export interface GatewayVersionedDefaultConfig {
legacy: LegacyGatewayConnector;
current: GatewayConnector;
}
export interface DataMapping {
topicFilter: string;
QoS: string;
QoS: string | number;
converter: Converter;
}
@ -180,12 +194,14 @@ export interface ConnectorSecurity {
mode?: ModeType;
}
export type ConnectorMapping = DeviceConnectorMapping | RequestMappingData | ConverterConnectorMapping;
export type ConnectorMapping = DeviceConnectorMapping | RequestMappingValue | ConverterConnectorMapping;
export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingFormValue | ConverterMappingFormValue;
export type ConnectorBaseConfig = ConnectorBaseInfo | MQTTBasicConfig | OPCBasicConfig | ModbusBasicConfig;
export type ConnectorLegacyBaseConfig = ConnectorBaseInfo | MQTTLegacyBasicConfig;
export interface ConnectorBaseInfo {
name: string;
id: string;
@ -194,10 +210,21 @@ export interface ConnectorBaseInfo {
}
export interface MQTTBasicConfig {
dataMapping: ConverterConnectorMapping[];
requestsMapping: Record<RequestType, RequestMappingData[]> | RequestMappingData[];
mapping: ConverterConnectorMapping[];
requestsMapping: Record<RequestType, RequestMappingData[] | RequestMappingValue[]> | RequestMappingData[] | RequestMappingValue[];
broker: BrokerConfig;
workers?: WorkersConfig;
}
export interface MQTTLegacyBasicConfig {
mapping: LegacyConverterConnectorMapping[];
broker: BrokerConfig;
workers?: WorkersConfig;
connectRequests: LegacyRequestMappingData[];
disconnectRequests: LegacyRequestMappingData[];
attributeRequests: LegacyRequestMappingData[];
attributeUpdates: LegacyRequestMappingData[];
serverSideRpc: LegacyRequestMappingData[];
}
export interface OPCBasicConfig {
@ -215,11 +242,11 @@ export interface WorkersConfig {
maxMessageNumberPerWorker: number;
}
interface DeviceInfo {
export interface ConnectorDeviceInfo {
deviceNameExpression: string;
deviceNameExpressionSource: string;
deviceNameExpressionSource: SourceType;
deviceProfileExpression: string;
deviceProfileExpressionSource: string;
deviceProfileExpressionSource: SourceType;
}
export interface Attribute {
@ -252,20 +279,41 @@ export interface AttributesUpdate {
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;
@ -274,8 +322,8 @@ export type ConverterMappingFormValue = Omit<ConverterConnectorMapping, 'convert
export interface DeviceConnectorMapping {
deviceNodePattern: string;
deviceNodeSource: string;
deviceInfo: DeviceInfo;
deviceNodeSource: SourceType;
deviceInfo: ConnectorDeviceInfo;
attributes?: Attribute[];
timeseries?: Timeseries[];
rpc_methods?: RpcMethod[];
@ -460,6 +508,7 @@ export interface GatewayLogData {
export interface AddConnectorConfigData {
dataSourceData: Array<any>;
gatewayVersion: string;
}
export interface CreatedConnectorConfigData {
@ -590,13 +639,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'
@ -607,33 +656,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 {
@ -707,9 +839,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',

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;

66
ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts

@ -0,0 +1,66 @@
///
/// 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,
LegacyGatewayConnector,
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { MqttVersionMappingUtil } from './mqtt-version-mapping.util';
export class GatewayConnectorVersionMappingUtil {
static getMappedByVersion(connector: GatewayConnector, gatewayVersion: string): GatewayConnector {
switch (connector.type) {
case ConnectorType.MQTT:
return this.getMappedMQTTByVersion(connector, gatewayVersion);
default:
return connector;
}
}
private static getMappedMQTTByVersion(
connector: GatewayConnector | LegacyGatewayConnector,
gatewayVersion: string
): GatewayConnector | LegacyGatewayConnector {
if (this.isVersionUpdateNeeded(gatewayVersion, connector.configVersion)) {
return this.isGatewayOutdated(gatewayVersion, connector.configVersion)
? MqttVersionMappingUtil.getLegacyVersion(connector)
: MqttVersionMappingUtil.getNewestVersion(connector);
}
return connector;
}
private static isGatewayOutdated(gatewayVersion: string, configVersion: string): boolean {
if (!gatewayVersion || !configVersion) {
return false;
}
return this.parseVersion(configVersion) > this.parseVersion(gatewayVersion);
}
private static isVersionUpdateNeeded(configVersion: string, gatewayVersion: string): boolean {
if (!gatewayVersion || !configVersion) {
return false;
}
return this.parseVersion(configVersion) !== this.parseVersion(gatewayVersion);
}
private static parseVersion(version: string): number {
return Number(version?.replace(/\./g, ''));
}
}

279
ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/mqtt-version-mapping.util.ts

@ -0,0 +1,279 @@
///
/// 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, isEqual } from '@core/utils';
import {
AttributeRequest,
ConnectorDeviceInfo,
Converter,
ConverterConnectorMapping,
ConvertorType,
CurrentGatewayConnector,
GatewayConnector,
LegacyConverter,
LegacyConverterConnectorMapping,
LegacyGatewayConnector,
LegacyRequestMappingData,
MQTTBasicConfig,
MQTTLegacyBasicConfig,
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 getNewestVersion(mqttConnector: GatewayConnector): CurrentGatewayConnector {
const {
connectRequests,
disconnectRequests,
attributeRequests,
attributeUpdates,
serverSideRpc
} = mqttConnector.configurationJson as MQTTLegacyBasicConfig;
let configurationJson = {
...mqttConnector.configurationJson,
requestsMapping: this.mapRequestsToNewestVersion({
connectRequests,
disconnectRequests,
attributeRequests,
attributeUpdates,
serverSideRpc
}),
mapping: this.mapMappingToNewestVersion((mqttConnector.configurationJson as MQTTLegacyBasicConfig).mapping),
};
this.mqttRequestTypeKeys.forEach((key: RequestType) => {
const { [key]: removedKey, ...rest } = configurationJson as MQTTLegacyBasicConfig;
configurationJson = { ...rest } as any;
});
this.cleanUpConfigJson(configurationJson as MQTTBasicConfig);
return {
...mqttConnector,
configurationJson
} as CurrentGatewayConnector;
}
static getLegacyVersion(mqttConnector: GatewayConnector): LegacyGatewayConnector {
const { requestsMapping, mapping, ...restConfig } = mqttConnector.configurationJson as MQTTBasicConfig;
const updatedRequestsMapping = this.mapRequestsToLegacyVersion(requestsMapping as Record<RequestType, RequestMappingData[]>);
const updatedMapping = this.mapMappingToLegacyVersion(mapping);
return {
...mqttConnector,
configurationJson: {
...restConfig,
...updatedRequestsMapping,
mapping: updatedMapping,
},
} as LegacyGatewayConnector;
}
static mapMappingToNewestVersion(mapping: LegacyConverterConnectorMapping[] | ConverterConnectorMapping[]): 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 };
});
}
static mapRequestsToNewestVersion(
requestMapping: Record<RequestType,
RequestMappingData[] | 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.mapRequestToNewest(value as LegacyRequestMappingData, key);
this.cleanUpOldFields(newValue as {});
return newValue;
});
return acc;
}, {}) as Record<RequestType, RequestMappingData[]>;
}
static mapRequestsToLegacyVersion(
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 mapMappingToLegacyVersion(
mapping: ConverterConnectorMapping[]
): LegacyConverterConnectorMapping[] {
return mapping?.map((converterMapping: ConverterConnectorMapping) => {
const converter = this.mapConverterToLegacy(converterMapping.converter);
this.cleanUpNewFields(converter as {});
return { converter, topicFilter: converterMapping.topicFilter };
});
}
private static mapConverterToLegacy(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 cleanUpConfigJson(configurationJson: MQTTBasicConfig): void {
if (isEqual(configurationJson.requestsMapping, {})) {
delete configurationJson.requestsMapping;
}
if (isEqual(configurationJson.mapping, [])) {
delete configurationJson.mapping;
}
}
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 mapRequestToNewest(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
};
}
}

8
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -113,7 +113,7 @@ 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';
@ -122,7 +122,7 @@ import {
} from '@home/components/widget/lib/gateway/connectors-configuration/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';
@ -145,6 +145,9 @@ 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';
@NgModule({
declarations: [
@ -233,6 +236,7 @@ import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/sc
ModbusBasicConfigComponent,
EllipsisChipListDirective,
ModbusRpcParametersComponent,
MqttLegacyBasicConfigComponent,
],
exports: [
EntitiesTableWidgetComponent,

597
ui-ngx/src/assets/metadata/connector-default-configs/mqtt.json

@ -1,218 +1,399 @@
{
"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": "anonymous"
}
},
"dataMapping": [
{
"topicFilter": "sensor/data",
"subscriptionQos": 1,
"converter": {
"type": "json",
"deviceInfo": {
"deviceNameExpressionSource": "message",
"deviceNameExpression": "${serialNumber}",
"deviceProfileExpressionSource": "message",
"deviceProfileExpression": "${sensorType}"
"current": {
"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": "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}"
}
]
}
}

Loading…
Cancel
Save