Browse Source

Merge pull request #11787 from maxunbearable/fix/4124-gateway-fixes_new-version-support

Gateway fixes and added support for newer Gateway versions
pull/11795/head
Igor Kulikov 2 years ago
committed by GitHub
parent
commit
7aa77faf78
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 22
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts
  2. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html
  3. 1
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts
  4. 8
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/dialog/add-connector-dialog.component.ts
  5. 8
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html
  6. 57
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  7. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts
  8. 32
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/latest-version-config.pipe.ts
  9. 9
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util.ts
  10. 10
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/opc-version-mapping.util.ts
  11. 7
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  12. 65
      ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_timeseries_expressions_fn.md
  13. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  14. 16
      ui-ngx/src/assets/metadata/connector-default-configs/modbus.json

22
ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/gateway-connector-version-processor.abstract.ts

@ -15,15 +15,17 @@
///
import { GatewayConnector, GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models';
import { isNumber, isString } from '@core/utils';
import {
GatewayConnectorVersionMappingUtil
} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util';
export abstract class GatewayConnectorVersionProcessor<BasicConfig> {
gatewayVersion: number;
configVersion: number;
protected constructor(protected gatewayVersionIn: string | number, protected connector: GatewayConnector<BasicConfig>) {
this.gatewayVersion = this.parseVersion(this.gatewayVersionIn);
this.configVersion = this.parseVersion(this.connector.configVersion);
this.gatewayVersion = GatewayConnectorVersionMappingUtil.parseVersion(this.gatewayVersionIn);
this.configVersion = GatewayConnectorVersionMappingUtil.parseVersion(this.connector.configVersion);
}
getProcessedByVersion(): GatewayConnector<BasicConfig> {
@ -53,19 +55,13 @@ export abstract class GatewayConnectorVersionProcessor<BasicConfig> {
}
private isVersionUpgradeNeeded(): boolean {
return this.gatewayVersionIn === GatewayVersion.Current && (!this.configVersion || this.configVersion < this.gatewayVersion);
return this.gatewayVersion >= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current)
&& (!this.configVersion || this.configVersion < this.gatewayVersion);
}
private isVersionDowngradeNeeded(): boolean {
return this.configVersion && this.connector.configVersion === GatewayVersion.Current && (this.configVersion > this.gatewayVersion);
}
private parseVersion(version: string | number): number {
if (isNumber(version)) {
return version as number;
}
return isString(version) ? parseFloat((version as string).replace(/\./g, '').slice(0, 3)) / 100 : 0;
return this.configVersion && this.configVersion >= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current)
&& (this.configVersion > this.gatewayVersion);
}
protected abstract getDowngradedVersion(): GatewayConnector<BasicConfig>;

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html

@ -175,7 +175,7 @@
<div class="tb-form-row" fxLayoutAlign="space-between center">
<mat-slide-toggle class="mat-slide" formControlName="sendDataToThingsBoard">
<mat-label>
{{ 'gateway.send-data-TB' | translate }}
{{ 'gateway.send-data-to-platform' | translate }}
</mat-label>
</mat-slide-toggle>
</div>

1
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component.ts

@ -126,6 +126,7 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On
if (!securityInfo.type) {
securityInfo.type = SecurityType.ANONYMOUS;
}
this.updateValidators(securityInfo.type);
this.securityFormGroup.reset(securityInfo, {emitEvent: false});
}
this.cdr.markForCheck();

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

@ -37,6 +37,7 @@ import { Observable, Subject } from 'rxjs';
import { ResourcesService } from '@core/services/resources.service';
import { takeUntil, tap } from 'rxjs/operators';
import { helpBaseUrl } from '@shared/models/constants';
import { LatestVersionConfigPipe } from '@home/components/widget/lib/gateway/pipes/latest-version-config.pipe';
@Component({
selector: 'tb-add-connector-dialog',
@ -63,6 +64,7 @@ export class AddConnectorDialogComponent
@Inject(MAT_DIALOG_DATA) public data: AddConnectorConfigData,
public dialogRef: MatDialogRef<AddConnectorDialogComponent, CreatedConnectorConfigData>,
private fb: FormBuilder,
private isLatestVersionConfig: LatestVersionConfigPipe,
private resourcesService: ResourcesService) {
super(store, router, dialogRef);
this.connectorForm = this.fb.group({
@ -103,9 +105,9 @@ export class AddConnectorDialogComponent
if (gatewayVersion) {
value.configVersion = gatewayVersion;
}
value.configurationJson = (gatewayVersion === GatewayVersion.Current
? defaultConfig[this.data.gatewayVersion]
: defaultConfig.legacy)
value.configurationJson = (this.isLatestVersionConfig.transform(gatewayVersion)
? defaultConfig[GatewayVersion.Current]
: defaultConfig[GatewayVersion.Legacy])
?? defaultConfig;
if (this.connectorForm.valid) {
this.dialogRef.close(value);

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

@ -178,7 +178,7 @@
<ng-container [ngSwitch]="initialConnector.type">
<ng-container *ngSwitchCase="ConnectorType.MQTT">
<tb-mqtt-basic-config
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
*ngIf="connectorForm.get('configVersion').value | isLatestVersionConfig else legacy"
formControlName="basicConfig"
[generalTabContent]="generalTabContent"
(initialized)="basicConfigInitSubject.next()"
@ -193,7 +193,7 @@
</ng-container>
<ng-container *ngSwitchCase="ConnectorType.OPCUA">
<tb-opc-ua-basic-config
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
*ngIf="connectorForm.get('configVersion').value | isLatestVersionConfig else legacy"
formControlName="basicConfig"
[generalTabContent]="generalTabContent"
(initialized)="basicConfigInitSubject.next()"
@ -208,7 +208,7 @@
</ng-container>
<ng-container *ngSwitchCase="ConnectorType.MODBUS">
<tb-modbus-basic-config
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
*ngIf="connectorForm.get('configVersion').value | isLatestVersionConfig else legacy"
formControlName="basicConfig"
[generalTabContent]="generalTabContent"
(initialized)="basicConfigInitSubject.next()"
@ -316,7 +316,7 @@
</div>
<tb-report-strategy
[defaultValue]="ReportStrategyDefaultValue.Connector"
*ngIf="connectorForm.get('type').value === ConnectorType.MODBUS && connectorForm.get('configVersion').value === GatewayVersion.Current"
*ngIf="connectorForm.get('type').value === ConnectorType.MODBUS && (connectorForm.get('configVersion').value | isLatestVersionConfig)"
formControlName="reportStrategy"
/>
</section>

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

@ -59,7 +59,6 @@ import {
GatewayConnectorDefaultTypesTranslatesMap,
GatewayLogLevel,
noLeadTrailSpacesRegex,
GatewayVersion,
ReportStrategyDefaultValue,
ReportStrategyType,
} from './gateway-widget.models';
@ -71,6 +70,7 @@ import { PageData } from '@shared/models/page/page-data';
import {
GatewayConnectorVersionMappingUtil
} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util';
import { LatestVersionConfigPipe } from '@home/components/widget/lib/gateway/pipes/latest-version-config.pipe';
export class ForceErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null): boolean {
@ -104,7 +104,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
readonly displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions'];
readonly GatewayConnectorTypesTranslatesMap = GatewayConnectorDefaultTypesTranslatesMap;
readonly ConnectorConfigurationModes = ConfigurationModes;
readonly GatewayVersion = GatewayVersion;
readonly ReportStrategyDefaultValue = ReportStrategyDefaultValue;
pageLink: PageLink;
@ -149,6 +148,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
private telemetryWsService: TelemetryWebsocketService,
private zone: NgZone,
private utils: UtilsService,
private isLatestVersionConfig: LatestVersionConfigPipe,
private cd: ChangeDetectorRef) {
super(store);
@ -255,7 +255,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
delete value.class;
}
if (value.type === ConnectorType.MODBUS && value.configVersion === GatewayVersion.Current) {
if (value.type === ConnectorType.MODBUS && this.isLatestVersionConfig.transform(value.configVersion)) {
if (!value.reportStrategy) {
value.reportStrategy = {
type: ReportStrategyType.OnReportPeriod,
@ -508,6 +508,9 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
if (!connector.configurationJson) {
connector.configurationJson = {} as ConnectorBaseConfig;
}
if (this.gatewayVersion && !connector.configVersion) {
connector.configVersion = this.gatewayVersion;
}
connector.basicConfig = connector.configurationJson;
this.initialConnector = connector;
@ -517,7 +520,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
this.saveConnector(this.getUpdatedConnectorData(connector));
if (!previousType || previousType === connector.type || !this.allowBasicConfig.has(connector.type)) {
if (previousType === connector.type || !this.allowBasicConfig.has(connector.type)) {
this.patchBasicConfigConnector(connector);
} else {
this.basicConfigInitSubject.pipe(take(1)).subscribe(() => {
@ -527,24 +530,26 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
}
private setInitialConnectorValues(connector: GatewayConnector): void {
const {basicConfig, mode, ...initialConnector} = connector;
this.toggleReportStrategy(connector.type);
this.connectorForm.get('mode').setValue(this.allowBasicConfig.has(connector.type)
? connector.mode ?? ConfigurationModes.BASIC
: null, {emitEvent: false}
);
this.connectorForm.get('configVersion').setValue(connector.configVersion, {emitEvent: false});
this.connectorForm.get('type').setValue(connector.type, {emitEvent: false});
this.connectorForm.patchValue(initialConnector, {emitEvent: false});
}
private openAddConnectorDialog(): Observable<GatewayConnector> {
return this.dialog.open<AddConnectorDialogComponent, AddConnectorConfigData>(AddConnectorDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
dataSourceData: this.dataSource.data,
gatewayVersion: this.gatewayVersion,
}
}).afterClosed();
return this.ctx.ngZone.run(() =>
this.dialog.open<AddConnectorDialogComponent, AddConnectorConfigData>(AddConnectorDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
dataSourceData: this.dataSource.data,
gatewayVersion: this.gatewayVersion,
}
}).afterClosed()
);
}
uniqNameRequired(): ValidatorFn {
@ -650,13 +655,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
private observeModeChange(): void {
this.connectorForm.get('mode').valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((mode) => {
.subscribe(() => {
this.connectorForm.get('mode').markAsPristine();
if (mode === ConfigurationModes.BASIC) {
this.basicConfigInitSubject.pipe(take(1)).subscribe(() => {
this.patchBasicConfigConnector({...this.initialConnector, mode: ConfigurationModes.BASIC});
});
}
});
}
@ -827,12 +827,17 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
...connector,
}, this.gatewayVersion);
if (this.gatewayVersion && !connectorState.configVersion) {
connectorState.configVersion = this.gatewayVersion;
}
connectorState.basicConfig = connectorState.configurationJson;
this.initialConnector = connectorState;
this.updateConnector(connectorState);
}
private updateConnector(connector: GatewayConnector): void {
this.jsonConfigSub?.unsubscribe();
switch (connector.type) {
case ConnectorType.MQTT:
case ConnectorType.OPCUA:
@ -842,18 +847,21 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
default:
this.connectorForm.patchValue({...connector, mode: null});
this.connectorForm.markAsPristine();
this.createJsonConfigWatcher();
}
this.createJsonConfigWatcher();
}
private updateBasicConfigConnector(connector: GatewayConnector): void {
this.basicConfigSub?.unsubscribe();
const previousType = this.connectorForm.get('type').value;
this.setInitialConnectorValues(connector);
if ((!connector.mode || connector.mode === ConfigurationModes.BASIC) && this.connectorForm.get('type').value !== connector.type) {
if (previousType === connector.type || !this.allowBasicConfig.has(connector.type)) {
this.patchBasicConfigConnector(connector);
} else {
this.basicConfigInitSubject.asObservable().pipe(take(1)).subscribe(() => {
this.patchBasicConfigConnector(connector);
});
} else {
this.patchBasicConfigConnector(connector);
}
}
@ -861,6 +869,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
this.connectorForm.patchValue(connector, {emitEvent: false});
this.connectorForm.markAsPristine();
this.createBasicConfigWatcher();
this.createJsonConfigWatcher();
}
private toggleReportStrategy(type: ConnectorType): void {

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/gateway-help-link.pipe.ts

@ -33,6 +33,8 @@ export class GatewayHelpLinkPipe implements PipeTransform {
} else {
return;
}
} else if (field === 'attributes' || field === 'timeseries') {
return 'widget/lib/gateway/attributes_timeseries_expressions_fn';
}
return 'widget/lib/gateway/expressions_fn';
}

32
ui-ngx/src/app/modules/home/components/widget/lib/gateway/pipes/latest-version-config.pipe.ts

@ -0,0 +1,32 @@
///
/// 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 { Pipe, PipeTransform } from '@angular/core';
import { GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models';
import {
GatewayConnectorVersionMappingUtil
} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util';
@Pipe({
name: 'isLatestVersionConfig',
standalone: true,
})
export class LatestVersionConfigPipe implements PipeTransform {
transform(configVersion: number | string): boolean {
return GatewayConnectorVersionMappingUtil.parseVersion(configVersion)
>= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current);
}
}

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

@ -24,6 +24,7 @@ import {
import { MqttVersionProcessor } from '@home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract';
import { OpcVersionProcessor } from '@home/components/widget/lib/gateway/abstract/opc-version-processor.abstract';
import { ModbusVersionProcessor } from '@home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract';
import { isNumber, isString } from '@core/utils';
export abstract class GatewayConnectorVersionMappingUtil {
@ -39,4 +40,12 @@ export abstract class GatewayConnectorVersionMappingUtil {
return connector;
}
}
static parseVersion(version: string | number): number {
if (isNumber(version)) {
return version as number;
}
return isString(version) ? parseFloat((version as string).replace(/\./g, '').slice(0, 3)) / 100 : 0;
}
}

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

@ -56,7 +56,7 @@ export class OpcVersionMappingUtil {
static mapMappingToUpgradedVersion(mapping: LegacyDeviceConnectorMapping[]): DeviceConnectorMapping[] {
return mapping.map((legacyMapping: LegacyDeviceConnectorMapping) => ({
...legacyMapping,
deviceNodeSource: this.getTypeSourceByValue(legacyMapping.deviceNodePattern),
deviceNodeSource: this.getDeviceNodeSourceByValue(legacyMapping.deviceNodePattern),
deviceInfo: {
deviceNameExpression: legacyMapping.deviceNamePattern,
deviceNameExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceNamePattern),
@ -122,6 +122,14 @@ export class OpcVersionMappingUtil {
return OPCUaSourceType.CONST;
}
private static getDeviceNodeSourceByValue(value: string): OPCUaSourceType {
if (value.includes('${')) {
return OPCUaSourceType.IDENTIFIER;
} else {
return OPCUaSourceType.PATH;
}
}
private static getArgumentType(arg: unknown): string {
switch (typeof arg) {
case 'boolean':

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

@ -167,9 +167,10 @@ import {
ModbusRpcParametersComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component';
import { RpcTemplateArrayViewPipe } from '@home/components/widget/lib/gateway/pipes/rpc-template-array-view.pipe';
import {
import {
ReportStrategyComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component';
import { LatestVersionConfigPipe } from '@home/components/widget/lib/gateway/pipes/latest-version-config.pipe';
@NgModule({
declarations: [
@ -268,6 +269,7 @@ import {
ModbusRpcParametersComponent,
RpcTemplateArrayViewPipe,
ReportStrategyComponent,
LatestVersionConfigPipe,
],
exports: [
EntitiesTableWidgetComponent,
@ -337,7 +339,8 @@ import {
ScadaSymbolWidgetComponent
],
providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule}
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule},
{provide: LatestVersionConfigPipe}
]
})
export class WidgetComponentsModule {

65
ui-ngx/src/assets/help/en_US/widget/lib/gateway/attributes_timeseries_expressions_fn.md

@ -0,0 +1,65 @@
### Expressions
#### JSON Path:
The expression field is used to extract data from the MQTT message. There are various available options for different parts of the messages:
- The JSONPath format can be used to extract data from the message body.
- The regular expression format can be used to extract data from the topic where the message will arrive.
- Slices can only be used in the expression fields of bytes converters.
JSONPath expressions specify the items within a JSON structure (which could be an object, array, or nested combination of both) that you want to access. These expressions can select elements from JSON data on specific criteria. Here's a basic overview of how JSONPath expressions are structured:
- `$`: The root element of the JSON document;
- `.`: Child operator used to select child elements. For example, $.store.book ;
- `[]`: Child operator used to select child elements. $['store']['book'] accesses the book array within a store object;
##### Examples:
For example, if we want to extract the device name from the following message, we can use the expression below:
MQTT message:
```
{
"sensorModelInfo": {
"sensorName": "AM-123",
"sensorType": "myDeviceType"
},
"data": {
"temp": 12.2,
"hum": 56,
"status": "ok"
}
}
{:copy-code}
```
Expression:
`${sensorModelInfo.sensorName}`
Converted data:
`AM-123`
If we want to extract all data from the message above, we can use the following expression:
`${data}`
Converted data:
`{"temp": 12.2, "hum": 56, "status": "ok"}`
Or if we want to extract specific data (for example “temperature”), you can use the following expression:
`${data.temp}`
And as a converted data we will get:
`12.2`
<br/>

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3277,7 +3277,7 @@
},
"select-connector": "Select connector to display config",
"send-change-data": "Send data only on change",
"send-data-TB": "Send data to ThingsBoard",
"send-data-to-platform": "Send data to platform",
"send-data-on-change": "Send data only on change",
"send-change-data-hint": "The values will be saved to the database only if they are different from the corresponding values in the previous converted message. This functionality applies to both attributes and time series in the converter output.",
"server": "Server",

16
ui-ngx/src/assets/metadata/connector-default-configs/modbus.json

@ -20,13 +20,21 @@
"connectAttemptTimeMs": 5000,
"connectAttemptCount": 5,
"waitAfterFailedAttemptsMs": 300000,
"reportStrategy": {
"type": "ON_REPORT_PERIOD",
"reportPeriod": 30000
},
"attributes": [
{
"tag": "string_read",
"type": "string",
"functionCode": 4,
"objectsCount": 4,
"address": 1
"address": 1,
"reportStrategy": {
"type": "ON_REPORT_PERIOD",
"reportPeriod": 15000
}
},
{
"tag": "bits_read",
@ -86,7 +94,11 @@
"type": "8uint",
"functionCode": 4,
"objectsCount": 1,
"address": 17
"address": 17,
"reportStrategy": {
"type": "ON_REPORT_PERIOD",
"reportPeriod": 15000
}
},
{
"tag": "16uint_read",

Loading…
Cancel
Save