Browse Source

Merge pull request #11354 from maxunbearable/improvements/050824-gateway

Gateway Improvements
pull/11373/head
Igor Kulikov 2 years ago
committed by GitHub
parent
commit
78257ee9f6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      ui-ngx/src/app/modules/common/modules-map.ts
  2. 13
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts
  3. 20
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html
  4. 10
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html
  5. 6
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.scss
  6. 40
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts
  7. 16
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html
  8. 5
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss
  9. 28
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts
  10. 37
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html
  11. 26
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts
  12. 92
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.html
  13. 166
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.ts
  14. 19
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html
  15. 27
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.scss
  16. 41
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts
  17. 10
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html
  18. 5
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.scss
  19. 10
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts
  20. 7
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.scss
  21. 39
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mqtt-basic-config/mqtt-basic-config.component.ts
  22. 31
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts
  23. 77
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  24. 40
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html
  25. 27
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts
  26. 28
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html
  27. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss
  28. 4
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts
  29. 67
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  30. 4
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  31. 18
      ui-ngx/src/app/shared/directives/public-api.ts
  32. 2
      ui-ngx/src/app/shared/directives/truncate-with-tooltip.directive.ts
  33. 1
      ui-ngx/src/app/shared/public-api.ts
  34. 25
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
ui-ngx/src/app/modules/common/modules-map.ts

@ -99,6 +99,9 @@ import * as TbJsonPipe from '@shared/pipe/tbJson.pipe';
import * as TruncatePipe from '@shared/pipe/truncate.pipe';
import * as ImagePipe from '@shared/pipe/image.pipe';
import * as EllipsisChipListDirective from '@shared/directives/ellipsis-chip-list.directive';
import * as TruncateWithTooltipDirective from '@shared/directives/truncate-with-tooltip.directive';
import * as coercion from '@shared/decorators/coercion';
import * as enumerable from '@shared/decorators/enumerable';
import * as TbInject from '@shared/decorators/tb-inject';
@ -422,6 +425,9 @@ class ModulesMap implements IModulesMap {
'@shared/pipe/truncate.pipe': TruncatePipe,
'@shared/pipe/image.pipe': ImagePipe,
'@shared/directives/ellipsis-chip-list.directive': EllipsisChipListDirective,
'@shared/directives/truncate-with-tooltip.directive': TruncateWithTooltipDirective,
'@shared/decorators/coercion': coercion,
'@shared/decorators/enumerable': enumerable,
'@shared/decorators/tb-inject': TbInject,

13
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts

@ -109,12 +109,13 @@ export class BrokerConfigControlComponent implements ControlValueAccessor, Valid
}
writeValue(brokerConfig: BrokerConfig): void {
const brokerConfigState = {
...brokerConfig,
version: brokerConfig.version || 5,
clientId: brokerConfig.clientId || 'tb_gw_' + generateSecret(5),
};
this.brokerConfigFormGroup.reset(brokerConfigState, {emitEvent: false});
const {
version = 5,
clientId = `tb_gw_${generateSecret(5)}`,
security = {},
} = brokerConfig;
this.brokerConfigFormGroup.reset({ ...brokerConfig, version, clientId, security }, { emitEvent: false });
this.cdr.markForCheck();
}

20
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html

@ -18,7 +18,7 @@
<div class="tb-mapping-table tb-absolute-fill">
<div fxFlex fxLayout="column" class="tb-mapping-table-content">
<mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="!textSearchMode">
<div class="mat-toolbar-tools">
<div class="mat-toolbar-tools" *ngIf="(dataSource.isEmpty() | async) === false">
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container">
<span class="tb-mapping-table-title">{{mappingTypeTranslationsMap.get(mappingType) | translate}}</span>
</div>
@ -75,15 +75,22 @@
</mat-header-cell>
<mat-cell *matCellDef="let mapping; let i = index"
[ngStyle.gt-md]="{ minWidth: '96px', maxWidth: '96px', width: '96px'}">
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
<ng-template #rowActions>
<button mat-icon-button
matTooltip="{{ 'action.edit' | translate }}"
matTooltipPosition="above"
(click)="manageMapping($event, i)">
<tb-icon>edit</tb-icon>
</button>
<button mat-icon-button
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above"
(click)="deleteMapping($event, i)">
<tb-icon>delete</tb-icon>
</button>
</ng-template>
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
<ng-container [ngTemplateOutlet]="rowActions"></ng-container>
</div>
<div fxHide fxShow.lt-lg fxFlex fxLayout="row" fxLayoutAlign="end">
<button mat-icon-button
@ -92,14 +99,7 @@
<mat-icon class="material-icons">more_vert</mat-icon>
</button>
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
<button mat-icon-button
(click)="manageMapping($event, i)">
<tb-icon>edit</tb-icon>
</button>
<button mat-icon-button
(click)="deleteMapping($event, i)">
<tb-icon>delete</tb-icon>
</button>
<ng-container [ngTemplateOutlet]="rowActions"></ng-container>
</mat-menu>
</div>
</mat-cell>

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

@ -23,6 +23,16 @@
<tb-modbus-master-table formControlName="master"></tb-modbus-master-table>
</mat-tab>
<mat-tab label="{{ 'gateway.server-config' | translate }}">
<div class="tb-form-panel no-border no-padding padding-top">
<div class="tb-form-hint tb-primary-fill tb-flex center">{{ 'gateway.hints.modbus-server' | translate }}</div>
<div class="tb-form-row" fxLayoutAlign="space-between center">
<mat-slide-toggle class="mat-slide" [formControl]="enableSlaveControl">
<mat-label>
{{ 'gateway.enable' | translate }}
</mat-label>
</mat-slide-toggle>
</div>
</div>
<tb-modbus-slave-config formControlName="slave"></tb-modbus-slave-config>
</mat-tab>
</mat-tab-group>

6
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.scss

@ -16,9 +16,3 @@
:host {
height: 100%;
}
:host ::ng-deep {
.mat-mdc-tab-body-content {
overflow: hidden !important;
}
}

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

@ -18,13 +18,15 @@ import { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, Templ
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormControl,
ValidationErrors,
Validator,
} from '@angular/forms';
import { ConnectorType, ModbusBasicConfig } from '@home/components/widget/lib/gateway/gateway-widget.models';
import { ModbusBasicConfig } 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';
@ -33,6 +35,7 @@ import { Subject } from 'rxjs';
import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive';
import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component';
import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component';
import { isEqual } from '@core/utils';
@Component({
selector: 'tb-modbus-basic-config',
@ -66,6 +69,7 @@ export class ModbusBasicConfigComponent implements ControlValueAccessor, Validat
@Input() generalTabContent: TemplateRef<any>;
basicFormGroup: FormGroup;
enableSlaveControl: FormControl<boolean>;
onChange: (value: ModbusBasicConfig) => void;
onTouched: () => void;
@ -77,13 +81,21 @@ export class ModbusBasicConfigComponent implements ControlValueAccessor, Validat
master: [],
slave: [],
});
this.enableSlaveControl = new FormControl(false);
this.basicFormGroup.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(value => {
this.onChange(value);
.subscribe(({ master, slave }) => {
this.onChange({ master, slave: slave ?? {} });
this.onTouched();
});
this.enableSlaveControl.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(enable => {
this.updateSlaveEnabling(enable);
this.basicFormGroup.get('slave').updateValueAndValidity({emitEvent: !!this.onChange});
});
}
ngOnDestroy(): void {
@ -106,11 +118,25 @@ export class ModbusBasicConfigComponent implements ControlValueAccessor, Validat
};
this.basicFormGroup.setValue(editedBase, {emitEvent: false});
this.enableSlaveControl.setValue(!!basicConfig.slave && !isEqual(basicConfig.slave, {}));
}
validate(): ValidationErrors | null {
return this.basicFormGroup.valid ? null : {
basicFormGroup: {valid: false}
};
validate(basicFormControl: UntypedFormControl): ValidationErrors | null {
const { master, slave } = basicFormControl.value;
const isEmpty = !master?.slaves?.length && (isEqual(slave, {}) || !slave);
if (!this.basicFormGroup.valid || isEmpty) {
return {
basicFormGroup: {valid: false}
};
}
return null;
}
private updateSlaveEnabling(isEnabled: boolean): void {
if (isEnabled) {
this.basicFormGroup.get('slave').enable({emitEvent: false});
} else {
this.basicFormGroup.get('slave').disable({emitEvent: false});
}
}
}

16
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html

@ -26,12 +26,16 @@
<mat-expansion-panel class="tb-settings" [expanded]="last">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<div class="title-container">
<span *ngIf="isMaster else tagName">
{{ keyControl.get('tag').value }}{{ '-' }}{{ keyControl.get('value').value }}
</span>
<ng-template #tagName>{{ keyControl.get('tag').value }}</ng-template>
<div *ngIf="isMaster else tagName" class="title-container" tbTruncateWithTooltip>
{{ keyControl.get('tag').value }}{{ '-' }}{{ keyControl.get('value').value }}
</div>
<ng-template #tagName>
<div class="tb-flex">
<div class="title-container" tbTruncateWithTooltip>{{ 'gateway.key' | translate }}: {{ keyControl.get('tag').value }}</div>
<div class="title-container">{{ 'gateway.address' | translate }}: {{ keyControl.get('address').value }}</div>
<div class="title-container">{{ 'gateway.type' | translate }}: {{ keyControl.get('type').value }}</div>
</div>
</ng-template>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
@ -98,7 +102,7 @@
name="value"
formControlName="objectsCount"
placeholder="{{ 'gateway.set' | translate }}"
[readonly]="!editableDataTypes.includes(keyControl.get('type').value)"
[readonly]="!ModbusEditableDataTypes.includes(keyControl.get('type').value)"
/>
</mat-form-field>
</div>

5
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss

@ -20,10 +20,7 @@
max-width: 700px;
.title-container {
max-width: 11vw;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap
width: 180px;
}
.key-panel {

28
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts

@ -27,6 +27,7 @@ import {
import { TbPopoverComponent } from '@shared/components/popover.component';
import {
ModbusDataType,
ModbusEditableDataTypes,
ModbusFunctionCodeTranslationsMap,
ModbusObjectCountByDataType,
ModbusValue,
@ -40,6 +41,7 @@ import { generateSecret } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({
selector: 'tb-modbus-data-keys-panel',
@ -50,6 +52,7 @@ import { Subject } from 'rxjs';
CommonModule,
SharedModule,
GatewayHelpLinkPipe,
TruncateWithTooltipDirective,
]
})
export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
@ -72,14 +75,15 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
functionCodesMap = new Map();
defaultFunctionCodes = [];
readonly editableDataTypes = [ModbusDataType.BYTES, ModbusDataType.BITS, ModbusDataType.STRING];
readonly ModbusEditableDataTypes = ModbusEditableDataTypes;
readonly ModbusFunctionCodeTranslationsMap = ModbusFunctionCodeTranslationsMap;
private destroy$ = new Subject<void>();
private readonly defaultReadFunctionCodes = [3, 4];
private readonly defaultWriteFunctionCodes = [5, 6, 15, 16];
private readonly stringAttrUpdatesWriteFunctionCodes = [6, 16];
private readonly bitsReadFunctionCodes = [1, 2];
private readonly defaultWriteFunctionCodes = [6, 16];
private readonly bitsWriteFunctionCodes = [5, 15];
constructor(private fb: UntypedFormBuilder) {}
@ -161,7 +165,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
private observeKeyDataType(keyFormGroup: FormGroup): void {
keyFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(dataType => {
if (!this.editableDataTypes.includes(dataType)) {
if (!this.ModbusEditableDataTypes.includes(dataType)) {
keyFormGroup.get('objectsCount').patchValue(ModbusObjectCountByDataType[dataType], {emitEvent: false});
}
this.updateFunctionCodes(keyFormGroup, dataType);
@ -177,23 +181,23 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
}
private getFunctionCodes(dataType: ModbusDataType): number[] {
const writeFunctionCodes = [
...(dataType === ModbusDataType.BITS ? this.bitsWriteFunctionCodes : []), ...this.defaultWriteFunctionCodes
];
if (this.keysType === ModbusValueKey.ATTRIBUTES_UPDATES) {
return dataType === ModbusDataType.STRING
? this.stringAttrUpdatesWriteFunctionCodes
: this.defaultWriteFunctionCodes;
return writeFunctionCodes.sort((a, b) => a - b);
}
const functionCodes = [...this.defaultReadFunctionCodes];
if (dataType === ModbusDataType.BITS) {
const bitsFunctionCodes = [1, 2];
functionCodes.push(...bitsFunctionCodes);
functionCodes.sort();
functionCodes.push(...this.bitsReadFunctionCodes);
}
if (this.keysType === ModbusValueKey.RPC_REQUESTS) {
functionCodes.push(...this.defaultWriteFunctionCodes);
functionCodes.push(...writeFunctionCodes);
}
return functionCodes;
return functionCodes.sort((a, b) => a - b);
}
private getDefaultFunctionCodes(): number[] {

37
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html

@ -16,9 +16,12 @@
-->
<div class="tb-master-table tb-absolute-fill">
<div class="tb-form-panel no-border no-padding padding-top">
<div class="tb-form-hint tb-primary-fill tb-flex center">{{ 'gateway.hints.modbus-master' | translate }}</div>
</div>
<div fxFlex fxLayout="column" class="tb-master-table-content">
<mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="!textSearchMode">
<div class="mat-toolbar-tools">
<div class="mat-toolbar-tools" *ngIf="(dataSource.isEmpty() | async) === false">
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container">
<span class="tb-master-table-title">{{ 'gateway.servers-slaves' | translate}}</span>
</div>
@ -63,33 +66,40 @@
<mat-header-cell *matHeaderCellDef class="table-value-column">
{{ 'gateway.name' | translate }}
</mat-header-cell>
<mat-cell *matCellDef="let mapping" class="table-value-column">
{{ mapping['name'] }}
<mat-cell *matCellDef="let slave" class="table-value-column">
{{ slave['name'] }}
</mat-cell>
</ng-container>
<ng-container [matColumnDef]="'type'">
<mat-header-cell *matHeaderCellDef class="table-value-column">
{{ 'gateway.client-communication-type' | translate }}
</mat-header-cell>
<mat-cell *matCellDef="let mapping" class="table-value-column">
{{ ModbusProtocolLabelsMap.get(mapping['type']) }}
<mat-cell *matCellDef="let slave" class="table-value-column">
{{ ModbusProtocolLabelsMap.get(slave['type']) }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef
[ngStyle.gt-md]="{ minWidth: '96px', maxWidth: '96px', width: '96px', textAlign: 'center'}">
</mat-header-cell>
<mat-cell *matCellDef="let mapping; let i = index"
<mat-cell *matCellDef="let slave; let i = index"
[ngStyle.gt-md]="{ minWidth: '96px', maxWidth: '96px', width: '96px'}">
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
<ng-template #rowActions>
<button mat-icon-button
matTooltip="{{ 'action.edit' | translate }}"
matTooltipPosition="above"
(click)="manageSlave($event, i)">
<tb-icon>edit</tb-icon>
</button>
<button mat-icon-button
(click)="deleteMapping($event, i)">
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above"
(click)="deleteSlave($event, i)">
<tb-icon>delete</tb-icon>
</button>
</ng-template>
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
<ng-container [ngTemplateOutlet]="rowActions"></ng-container>
</div>
<div fxHide fxShow.lt-lg fxFlex fxLayout="row" fxLayoutAlign="end">
<button mat-icon-button
@ -98,20 +108,13 @@
<mat-icon class="material-icons">more_vert</mat-icon>
</button>
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
<button mat-icon-button
(click)="manageSlave($event, i)">
<tb-icon>edit</tb-icon>
</button>
<button mat-icon-button
(click)="deleteMapping($event, i)">
<tb-icon>delete</tb-icon>
</button>
<ng-container [ngTemplateOutlet]="rowActions"></ng-container>
</mat-menu>
</div>
</mat-cell>
</ng-container>
<mat-header-row [ngClass]="{'mat-row-select': true}" *matHeaderRowDef="['name', 'type', 'actions']; sticky: true"></mat-header-row>
<mat-row *matRowDef="let mapping; columns: ['name', 'type', 'actions']"></mat-row>
<mat-row *matRowDef="let slave; columns: ['name', 'type', 'actions']"></mat-row>
</table>
<section [fxShow]="!textSearchMode && (dataSource.isEmpty() | async)" fxLayoutAlign="center center"
class="mat-headline-5 tb-absolute-fill tb-add-new">

26
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts

@ -34,18 +34,17 @@ import {
ControlValueAccessor,
FormArray,
FormBuilder,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormGroup,
ValidationErrors,
Validator,
} from '@angular/forms';
import {
ModbusMasterConfig,
ModbusProtocolLabelsMap,
ModbusSlaveInfo,
ModbusValues,
SlaveConfig
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils';
import { isDefinedAndNotNull } from '@core/utils';
import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import { ModbusSlaveDialogComponent } from '../modbus-slave-dialog/modbus-slave-dialog.component';
@ -62,16 +61,11 @@ import { TbTableDatasource } from '@shared/components/table/table-datasource.abs
useExisting: forwardRef(() => ModbusMasterTableComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ModbusMasterTableComponent),
multi: true
}
],
standalone: true,
imports: [CommonModule, SharedModule]
})
export class ModbusMasterTableComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy {
export class ModbusMasterTableComponent implements ControlValueAccessor, AfterViewInit, OnInit, OnDestroy {
@ViewChild('searchInput') searchInputField: ElementRef;
@ -138,12 +132,6 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, Validat
this.pushDataAsFormArrays(master.slaves);
}
validate(): ValidationErrors | null {
return this.slaves.controls.length ? null : {
slavesFormGroup: {valid: false}
};
}
enterFilterMode(): void {
this.textSearchMode = true;
this.cdr.detectChanges();
@ -164,12 +152,12 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, Validat
}
const withIndex = isDefinedAndNotNull(index);
const value = withIndex ? this.slaves.at(index).value : {};
this.dialog.open<ModbusSlaveDialogComponent, any, any>(ModbusSlaveDialogComponent, {
this.dialog.open<ModbusSlaveDialogComponent, ModbusSlaveInfo, ModbusValues>(ModbusSlaveDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
value,
buttonTitle: withIndex ? 'action.add' : 'action.apply'
buttonTitle: withIndex ? 'action.apply' : 'action.add'
}
}).afterClosed()
.pipe(take(1), takeUntil(this.destroy$))
@ -185,7 +173,7 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, Validat
});
}
deleteMapping($event: Event, index: number): void {
deleteSlave($event: Event, index: number): void {
if ($event) {
$event.stopPropagation();
}

92
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.html

@ -0,0 +1,92 @@
<!--
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.
-->
<ng-container [formGroup]="rpcParametersFormGroup">
<div fxFlex fxLayout="row">
<mat-form-field fxFlex="100">
<mat-label>{{ 'gateway.key' | translate }}</mat-label>
<input matInput name="value" formControlName="tag" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.key-required') | translate"
*ngIf="rpcParametersFormGroup.get('tag').hasError('required') &&
rpcParametersFormGroup.get('tag').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label>{{ 'gateway.rpc.type' | translate }}</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let type of modbusDataTypes" [value]="type">{{ type }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50" class="mat-block">
<mat-label>{{ 'gateway.rpc.functionCode' | translate }}</mat-label>
<mat-select formControlName="functionCode">
<mat-option *ngFor="let code of functionCodes" [value]="code">{{ ModbusFunctionCodeTranslationsMap.get(code) | translate}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex fxLayout="row">
<mat-form-field fxFlex="100" *ngIf="writeFunctionCodes.includes(rpcParametersFormGroup.get('functionCode').value)">
<mat-label>{{ 'gateway.rpc.value' | translate }}</mat-label>
<input matInput name="value" formControlName="value" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.value-required') | translate"
*ngIf="rpcParametersFormGroup.get('value').hasError('required') &&
rpcParametersFormGroup.get('value').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.address' | translate }}</mat-label>
<input matInput type="number" min="0" max="50000" name="value" formControlName="address" placeholder="{{ 'gateway.set' | translate }}"/>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.address-required') | translate"
*ngIf="rpcParametersFormGroup.get('address').hasError('required') &&
rpcParametersFormGroup.get('address').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.objectsCount' | translate }}</mat-label>
<input
matInput
type="number"
min="1"
max="50000"
name="value"
formControlName="objectsCount"
placeholder="{{ 'gateway.set' | translate }}"
[readonly]="!ModbusEditableDataTypes.includes(rpcParametersFormGroup.get('type').value)"
/>
</mat-form-field>
</div>
</ng-container>

166
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.ts

@ -0,0 +1,166 @@
///
/// 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,
OnDestroy,
} from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormGroup,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
ModbusDataType,
ModbusEditableDataTypes,
ModbusFunctionCodeTranslationsMap,
ModbusObjectCountByDataType,
ModbusValue,
noLeadTrailSpacesRegex,
} from '@home/components/widget/lib/gateway/gateway-widget.models';
@Component({
selector: 'tb-modbus-rpc-parameters',
templateUrl: './modbus-rpc-parameters.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ModbusRpcParametersComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ModbusRpcParametersComponent),
multi: true
}
],
standalone: true,
imports: [
CommonModule,
SharedModule,
],
})
export class ModbusRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy {
rpcParametersFormGroup: UntypedFormGroup;
functionCodes: Array<number>;
readonly ModbusEditableDataTypes = ModbusEditableDataTypes;
readonly ModbusFunctionCodeTranslationsMap = ModbusFunctionCodeTranslationsMap;
readonly modbusDataTypes = Object.values(ModbusDataType) as ModbusDataType[];
readonly writeFunctionCodes = [5, 6, 15, 16];
private readonly defaultFunctionCodes = [3, 4, 6, 16];
private readonly readFunctionCodes = [1, 2, 3, 4];
private readonly bitsFunctionCodes = [...this.readFunctionCodes, ...this.writeFunctionCodes];
private onChange: (value: ModbusValue) => void;
private onTouched: () => void;
private destroy$ = new Subject<void>();
constructor(private fb: FormBuilder) {
this.rpcParametersFormGroup = this.fb.group({
tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
type: [ModbusDataType.BYTES, [Validators.required]],
functionCode: [this.defaultFunctionCodes[0], [Validators.required]],
value: [{value: '', disabled: true}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
address: [null, [Validators.required]],
objectsCount: [1, [Validators.required]],
});
this.updateFunctionCodes(this.rpcParametersFormGroup.get('type').value);
this.observeValueChanges();
this.observeKeyDataType();
this.observeFunctionCode();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
registerOnChange(fn: (value: ModbusValue) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
validate(): ValidationErrors | null {
return this.rpcParametersFormGroup.valid ? null : {
rpcParametersFormGroup: { valid: false }
};
}
writeValue(value: ModbusValue): void {
this.rpcParametersFormGroup.patchValue(value, {emitEvent: false});
}
private observeValueChanges(): void {
this.rpcParametersFormGroup.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
this.onChange(value);
this.onTouched();
});
}
private observeKeyDataType(): void {
this.rpcParametersFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(dataType => {
if (!this.ModbusEditableDataTypes.includes(dataType)) {
this.rpcParametersFormGroup.get('objectsCount').patchValue(ModbusObjectCountByDataType[dataType], {emitEvent: false});
}
this.updateFunctionCodes(dataType);
});
}
private observeFunctionCode(): void {
this.rpcParametersFormGroup.get('functionCode').valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(code => this.updateValueEnabling(code));
}
private updateValueEnabling(code: number): void {
if (this.writeFunctionCodes.includes(code)) {
this.rpcParametersFormGroup.get('value').enable({emitEvent: false});
} else {
this.rpcParametersFormGroup.get('value').setValue(null);
this.rpcParametersFormGroup.get('value').disable({emitEvent: false});
}
}
private updateFunctionCodes(dataType: ModbusDataType): void {
this.functionCodes = dataType === ModbusDataType.BITS ? this.bitsFunctionCodes : this.defaultFunctionCodes;
if (!this.functionCodes.includes(this.rpcParametersFormGroup.get('functionCode').value)) {
this.rpcParametersFormGroup.get('functionCode').patchValue(this.functionCodes[0], {emitEvent: false});
}
}
}

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

@ -16,16 +16,6 @@
-->
<div [formGroup]="slaveConfigFormGroup" class="slave-container">
<div class="tb-form-panel no-border no-padding padding-top">
<div class="tb-form-hint tb-primary-fill tb-flex center">{{ 'gateway.hints.modbus-server' | translate }}</div>
<div class="tb-form-row" fxLayoutAlign="space-between center">
<mat-slide-toggle class="mat-slide" formControlName="sendDataToThingsBoard">
<mat-label>
{{ 'gateway.enable' | translate }}
</mat-label>
</mat-slide-toggle>
</div>
</div>
<div class="slave-content tb-form-panel no-border no-padding padding-top" >
<div class="tb-flex row space-between align-center no-gap fill-width">
<div class="fixed-title-width" translate>gateway.server-slave-config</div>
@ -161,7 +151,7 @@
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" translate>gateway.poll-period</div>
<div class="fixed-title-width tb-required" translate>gateway.poll-period</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" name="value" formControlName="pollPeriod" placeholder="{{ 'gateway.set' | translate }}"/>
@ -178,6 +168,13 @@
</mat-form-field>
</div>
</div>
<div class="tb-form-row" fxLayoutAlign="space-between center">
<mat-slide-toggle class="mat-slide" formControlName="sendDataToThingsBoard">
<mat-label>
{{ 'gateway.send-data-TB' | translate }}
</mat-label>
</mat-slide-toggle>
</div>
<div class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>

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

@ -1,27 +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.
*/
$server-config-header-height: 132px;
:host {
.slave-content {
height: calc(100% - #{$server-config-header-height});
overflow: auto;
}
.slave-container {
display: inherit;
}
}

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

@ -43,7 +43,7 @@ import {
import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import { Subject } from 'rxjs';
import { startWith, takeUntil } from 'rxjs/operators';
import { takeUntil } from 'rxjs/operators';
import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe';
import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component';
import { ModbusValuesComponent, } from '../modbus-values/modbus-values.component';
@ -73,7 +73,6 @@ import { isEqual } from '@core/utils';
ModbusSecurityConfigComponent,
GatewayPortTooltipPipe,
],
styleUrls: ['./modbus-slave-config.component.scss'],
})
export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validator, OnDestroy {
@ -90,6 +89,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
readonly ModbusProtocolType = ModbusProtocolType;
readonly modbusBaudrates = ModbusBaudrates;
private isSlaveEnabled = false;
private readonly serialSpecificControlKeys = ['serialPort', 'baudrate'];
private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host'];
@ -106,11 +106,11 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]],
serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
method: [ModbusMethodType.SOCKET],
unitId: [0, [Validators.required]],
unitId: [null, [Validators.required]],
baudrate: [this.modbusBaudrates[0]],
deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
pollPeriod: [5000],
pollPeriod: [5000, [Validators.required]],
sendDataToThingsBoard: [false],
byteOrder:[ModbusOrderType.BIG],
security: [],
@ -126,14 +126,9 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
this.observeValueChanges();
this.observeTypeChange();
this.observeFormEnable();
this.observeShowSecurity();
}
get isSlaveEnabled(): boolean {
return this.slaveConfigFormGroup.get('sendDataToThingsBoard').value;
}
get protocolType(): ModbusProtocolType {
return this.slaveConfigFormGroup.get('type').value;
}
@ -160,7 +155,11 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
writeValue(slaveConfig: ModbusSlave): void {
this.showSecurityControl.patchValue(!!slaveConfig.security && !isEqual(slaveConfig.security, {}));
this.updateSlaveConfig(slaveConfig);
this.updateFormEnableState(slaveConfig.sendDataToThingsBoard);
}
setDisabledState(isDisabled: boolean): void {
this.isSlaveEnabled = !isDisabled;
this.updateFormEnableState();
}
private observeValueChanges(): void {
@ -180,7 +179,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
this.slaveConfigFormGroup.get('type').valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(type => {
this.updateFormEnableState(this.isSlaveEnabled);
this.updateFormEnableState();
this.updateMethodType(type);
});
}
@ -196,22 +195,15 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
}
}
private observeFormEnable(): void {
this.slaveConfigFormGroup.get('sendDataToThingsBoard').valueChanges
.pipe(startWith(this.isSlaveEnabled), takeUntil(this.destroy$))
.subscribe(value => this.updateFormEnableState(value));
}
private updateFormEnableState(enabled: boolean): void {
if (enabled) {
private updateFormEnableState(): void {
if (this.isSlaveEnabled) {
this.slaveConfigFormGroup.enable({emitEvent: false});
this.showSecurityControl.enable({emitEvent: false});
} else {
this.slaveConfigFormGroup.disable({emitEvent: false});
this.showSecurityControl.disable({emitEvent: false});
this.slaveConfigFormGroup.get('sendDataToThingsBoard').enable({emitEvent: false});
}
this.updateEnablingByProtocol(this.protocolType);
this.updateEnablingByProtocol();
this.updateSecurityEnable(this.showSecurityControl.value);
}
@ -229,9 +221,10 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
}
}
private updateEnablingByProtocol(type: ModbusProtocolType): void {
const enableKeys = type === ModbusProtocolType.Serial ? this.serialSpecificControlKeys : this.tcpUdpSpecificControlKeys;
const disableKeys = type === ModbusProtocolType.Serial ? this.tcpUdpSpecificControlKeys : this.serialSpecificControlKeys;
private updateEnablingByProtocol(): void {
const isSerial = this.protocolType === ModbusProtocolType.Serial;
const enableKeys = isSerial ? this.serialSpecificControlKeys : this.tcpUdpSpecificControlKeys;
const disableKeys = isSerial ? this.tcpUdpSpecificControlKeys : this.serialSpecificControlKeys;
if (this.isSlaveEnabled) {
enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false }));

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

@ -28,7 +28,7 @@
</mat-toolbar>
<div mat-dialog-content [formGroup]="slaveConfigFormGroup" class="tb-form-panel">
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width tb-required" translate>gateway.name</div>
<div class="fixed-title-width slave-name-label tb-required" translate>gateway.name</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput name="value" formControlName="name" placeholder="{{ 'gateway.set' | translate }}"/>
@ -306,7 +306,7 @@
</mat-slide-toggle>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width-230" translate>gateway.poll-period</div>
<div class="fixed-title-width-230 tb-required" translate>gateway.poll-period</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" name="value" formControlName="pollPeriod" placeholder="{{ 'gateway.set' | translate }}"/>
@ -314,7 +314,7 @@
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width-230" translate>gateway.connect-attempt-time</div>
<div class="fixed-title-width-230 tb-required" translate>gateway.connect-attempt-time</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" name="value" formControlName="connectAttemptTimeMs" placeholder="{{ 'gateway.set' | translate }}"/>
@ -322,7 +322,7 @@
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width-230" translate>gateway.connect-attempt-count</div>
<div class="fixed-title-width-230 tb-required" translate>gateway.connect-attempt-count</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" name="value" formControlName="connectAttemptCount" placeholder="{{ 'gateway.set' | translate }}"/>
@ -330,7 +330,7 @@
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width-230" translate>gateway.wait-after-failed-attempts</div>
<div class="fixed-title-width-230 tb-required" translate>gateway.wait-after-failed-attempts</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" name="value" formControlName="waitAfterFailedAttemptsMs" placeholder="{{ 'gateway.set' | translate }}"/>

5
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.scss

@ -18,4 +18,9 @@
width: 80vw;
max-width: 900px;
}
.slave-name-label {
margin-right: 16px;
color: rgba(0, 0, 0, 0.87);
}
}

10
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts

@ -163,7 +163,7 @@ export class ModbusSlaveDialogComponent extends DialogComponent<ModbusSlaveDialo
bytesize: [ModbusByteSizes[0]],
parity: [ModbusParity.None],
strict: [true],
unitId: [0, [Validators.required]],
unitId: [null, [Validators.required]],
deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
sendDataOnlyOnChange: [false],
@ -173,10 +173,10 @@ export class ModbusSlaveDialogComponent extends DialogComponent<ModbusSlaveDialo
retries: [true],
retryOnEmpty: [true],
retryOnInvalid: [true],
pollPeriod: [5000],
connectAttemptTimeMs: [5000],
connectAttemptCount: [5],
waitAfterFailedAttemptsMs: [300000],
pollPeriod: [5000, [Validators.required]],
connectAttemptTimeMs: [5000, [Validators.required]],
connectAttemptCount: [5, [Validators.required]],
waitAfterFailedAttemptsMs: [300000, [Validators.required]],
values: [{}],
security: [{}],
});

7
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.scss

@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.mat-mdc-tab-body-wrapper {
min-height: 320px;
}
:host ::ng-deep .mat-mdc-tab-body-wrapper {
min-height: 320px;
}
::ng-deep .mdc-evolution-chip-set__chips {

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

@ -47,6 +47,7 @@ import {
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',
@ -84,7 +85,7 @@ export class MqttBasicConfigComponent implements ControlValueAccessor, Validator
mappingTypes = MappingType;
basicFormGroup: FormGroup;
private onChange: (value: string) => void;
private onChange: (value: MQTTBasicConfig) => void;
private onTouched: () => void;
private destroy$ = new Subject<void>();
@ -100,7 +101,7 @@ export class MqttBasicConfigComponent implements ControlValueAccessor, Validator
this.basicFormGroup.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(value => {
this.onChange(value);
this.onChange(this.getMappedMQTTConfig(value));
this.onTouched();
});
}
@ -110,7 +111,7 @@ export class MqttBasicConfigComponent implements ControlValueAccessor, Validator
this.destroy$.complete();
}
registerOnChange(fn: (value: string) => void): void {
registerOnChange(fn: (value: MQTTBasicConfig) => void): void {
this.onChange = fn;
}
@ -135,13 +136,30 @@ export class MqttBasicConfigComponent implements ControlValueAccessor, Validator
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[] {
private getRequestDataArray(value: Record<RequestType, RequestMappingData[]>): RequestMappingData[] {
const mappingConfigs = [];
if (isObject(value)) {
@ -157,4 +175,17 @@ export class MqttBasicConfigComponent implements ControlValueAccessor, Validator
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: [],
});
}
}

31
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts

@ -85,7 +85,7 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator
subCheckPeriodInMillis: [10, [Validators.required, Validators.min(10)]],
showMap: [false, []],
security: [SecurityPolicy.BASIC128, []],
identity: [{}, [Validators.required]]
identity: []
});
this.serverConfigFormGroup.valueChanges.pipe(
@ -116,16 +116,25 @@ export class OpcServerConfigComponent implements ControlValueAccessor, Validator
}
writeValue(serverConfig: ServerConfig): void {
const { timeoutInMillis, scanPeriodInMillis, enableSubscriptions, subCheckPeriodInMillis, showMap, security } = serverConfig;
const serverConfigState = {
const {
timeoutInMillis = 1000,
scanPeriodInMillis = 1000,
enableSubscriptions = true,
subCheckPeriodInMillis = 10,
showMap = false,
security = SecurityPolicy.BASIC128,
identity = {},
} = serverConfig;
this.serverConfigFormGroup.reset({
...serverConfig,
timeoutInMillis: timeoutInMillis || 1000,
scanPeriodInMillis: scanPeriodInMillis || 1000,
enableSubscriptions: enableSubscriptions || true,
subCheckPeriodInMillis: subCheckPeriodInMillis || 10,
showMap: showMap || false,
security: security || SecurityPolicy.BASIC128,
};
this.serverConfigFormGroup.reset(serverConfigState, {emitEvent: false});
timeoutInMillis,
scanPeriodInMillis,
enableSubscriptions,
subCheckPeriodInMillis,
showMap,
security,
identity,
}, { emitEvent: false });
}
}

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

@ -51,6 +51,7 @@ import { EntityType } from '@shared/models/entity-type.models';
import {
AddConnectorConfigData,
ConnectorBaseConfig,
ConnectorBaseInfo,
ConnectorConfigurationModes,
ConnectorType,
GatewayConnector,
@ -60,7 +61,7 @@ import {
} from './gateway-widget.models';
import { MatDialog } from '@angular/material/dialog';
import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component';
import { debounceTime, take, takeUntil, tap } from 'rxjs/operators';
import { debounceTime, filter, take, takeUntil, tap } from 'rxjs/operators';
import { ErrorStateMatcher } from '@angular/material/core';
import { PageData } from '@shared/models/page/page-data';
@ -133,6 +134,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
private basicConfigSub: Subscription;
private jsonConfigSub: Subscription;
private subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {
onDataUpdated: () => this.ctx.ngZone.run(() => {
@ -209,17 +212,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
}
});
this.connectorForm.get('configurationJson').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((config) => {
const basicConfig = this.connectorForm.get('basicConfig');
const type = this.connectorForm.get('type').value;
const mode = this.connectorForm.get('mode').value;
if (!isEqual(config, basicConfig?.value) && this.allowBasicConfig.has(type) && mode === ConnectorConfigurationModes.ADVANCED) {
this.connectorForm.get('basicConfig').patchValue(config, {emitEvent: false});
}
});
this.dataSource.sort = this.sort;
this.dataSource.sortingDataAccessor = (data: AttributeData, sortHeaderId: string) => {
switch (sortHeaderId) {
@ -275,7 +267,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
}
saveConnector(): void {
const value = this.connectorForm.get('type').value === ConnectorType.MQTT ? this.getMappedMQTTValue() : this.connectorForm.value;
const value = { ...this.connectorForm.value };
value.configuration = camelCase(value.name) + '.json';
delete value.basicConfig;
if (value.type !== ConnectorType.GRPC) {
@ -336,20 +328,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
});
}
private getMappedMQTTValue(): GatewayConnector {
const value = this.connectorForm.value;
return {
...value,
configurationJson: {
...value.configurationJson,
broker: {
...value.configurationJson.broker,
...value.configurationJson.workers,
}
}
};
}
private updateData(reload: boolean = false): void {
this.pageLink.sortOrder.property = this.sort.active;
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
@ -383,11 +361,28 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
}
const sharedIndex = this.sharedAttributeData.findIndex(data => {
const sharedData = data.value;
return sharedData.name === connectorData.name && sharedData.ts && sharedData.ts <= connectorData.ts;
const hasSameName = sharedData.name === connectorData.name;
const hasEmptyConfig = isEqual(sharedData.configurationJson, {}) && hasSameName;
const hasSameConfig = this.hasSameConfig(sharedData.configurationJson, connectorData.configurationJson);
const isRecentlyCreated = sharedData.ts && sharedData.ts <= connectorData.ts;
return hasSameName && isRecentlyCreated && (hasSameConfig || hasEmptyConfig);
});
return sharedIndex !== -1;
}
private hasSameConfig(sharedDataConfigJson: ConnectorBaseInfo, connectorDataConfigJson: ConnectorBaseInfo): boolean {
const { name, id, enableRemoteLogging, logLevel, ...sharedDataConfig } = sharedDataConfigJson;
const {
name: connectorName,
id: connectorId,
enableRemoteLogging: connectorEnableRemoteLogging,
logLevel: connectorLogLevel,
...connectorConfig
} = connectorDataConfigJson;
return isEqual(sharedDataConfig, connectorConfig);
}
private combineData(): void {
this.dataSource.data = [...this.activeData, ...this.inactiveData, ...this.sharedAttributeData].filter((item, index, self) =>
index === self.findIndex((t) => t.key === item.key)
@ -684,6 +679,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
this.basicConfigSub.unsubscribe();
}
this.basicConfigSub = this.connectorForm.get('basicConfig').valueChanges.pipe(
filter(() => !!this.initialConnector),
takeUntil(this.destroy$)
).subscribe((config) => {
const configJson = this.connectorForm.get('configurationJson');
@ -696,6 +692,22 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
});
}
private createJsonConfigWatcher(): void {
if (this.jsonConfigSub) {
this.jsonConfigSub.unsubscribe();
}
this.jsonConfigSub = this.connectorForm.get('configurationJson').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((config) => {
const basicConfig = this.connectorForm.get('basicConfig');
const type = this.connectorForm.get('type').value;
const mode = this.connectorForm.get('mode').value;
if (!isEqual(config, basicConfig?.value) && this.allowBasicConfig.has(type) && mode === ConnectorConfigurationModes.ADVANCED) {
this.connectorForm.get('basicConfig').patchValue(config, {emitEvent: false});
}
});
}
private confirmConnectorChange(): Observable<boolean> {
if (this.initialConnector && this.connectorForm.dirty) {
return this.dialogService.confirm(
@ -734,19 +746,18 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
case ConnectorType.MQTT:
case ConnectorType.OPCUA:
case ConnectorType.MODBUS:
this.connectorForm.get('type').patchValue(connector.type, {emitValue: false, onlySelf: true});
this.connectorForm.get('basicConfig').setValue({}, {emitEvent: false});
this.connectorForm.get('mode').setValue(connector.mode || ConnectorConfigurationModes.BASIC, {emitEvent: false});
setTimeout(() => {
this.connectorForm.patchValue({...connector, mode: connector.mode || ConnectorConfigurationModes.BASIC});
this.createBasicConfigWatcher();
this.connectorForm.patchValue(connector, {emitEvent: false});
this.connectorForm.markAsPristine();
this.createBasicConfigWatcher();
});
break;
default:
this.connectorForm.patchValue({...connector, mode: null});
this.connectorForm.markAsPristine();
}
this.createJsonConfigWatcher();
}
private setClientData(data: PageData<AttributeData>): void {

40
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html

@ -50,46 +50,6 @@
placeholder="${params}"/>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.MODBUS">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.tag' | translate }}</mat-label>
<input matInput formControlName="tag" placeholder="setValue"/>
</mat-form-field>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label>{{ 'gateway.rpc.type' | translate }}</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let type of modbusCommandTypes" [value]="type">
{{ type }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50" class="mat-block">
<mat-label>{{ 'gateway.rpc.functionCode' | translate }}</mat-label>
<mat-select formControlName="functionCode">
<mat-option *ngFor="let code of codesArray" [value]="code">
{{ modbusCodesTranslate.get(code) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<mat-form-field *ngIf="commandForm.get('functionCode').value>4">
<mat-label>{{ 'gateway.rpc.value' | translate }}</mat-label>
<input matInput formControlName="value" placeholder="value"/>
</mat-form-field>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.address' | translate }}</mat-label>
<input matInput formControlName="address" type="number" min="0"
placeholder="1" step="1"/>
</mat-form-field>
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.objectsCount' | translate }}</mat-label>
<input matInput formControlName="objectsCount" type="number" min="0"
placeholder="31" step="1"/>
</mat-form-field>
</div>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.BACNET">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>

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

@ -35,8 +35,6 @@ import {
ConnectorType,
GatewayConnectorDefaultTypesTranslatesMap,
HTTPMethods,
ModbusCodesTranslate,
ModbusCommandTypes,
noLeadTrailSpacesRegex,
RPCCommand,
RPCTemplateConfig,
@ -53,7 +51,7 @@ import {
} from '@shared/components/dialog/json-object-edit-dialog.component';
import { jsonRequired } from '@shared/components/json-object-edit.component';
import { deepClone } from '@core/utils';
import { filter, takeUntil, tap } from "rxjs/operators";
import { takeUntil, tap } from "rxjs/operators";
import { Subject } from "rxjs";
@Component({
@ -80,9 +78,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
saveTemplate: EventEmitter<RPCTemplateConfig> = new EventEmitter();
commandForm: FormGroup;
codesArray: Array<number> = [1, 2, 3, 4, 5, 6, 15, 16];
ConnectorType = ConnectorType;
modbusCommandTypes = Object.values(ModbusCommandTypes) as ModbusCommandTypes[];
bACnetRequestTypes = Object.values(BACnetRequestTypes) as BACnetRequestTypes[];
bACnetObjectTypes = Object.values(BACnetObjectTypes) as BACnetObjectTypes[];
bLEMethods = Object.values(BLEMethods) as BLEMethods[];
@ -98,7 +94,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
SocketMethodProcessingsTranslates = SocketMethodProcessingsTranslates;
SNMPMethodsTranslations = SNMPMethodsTranslations;
gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslatesMap;
modbusCodesTranslate = ModbusCodesTranslate;
urlPattern = /^[-a-zA-Zd_$:{}?~+=\/.0-9-]*$/;
numbersOnlyPattern = /^[0-9]*$/;
@ -156,26 +151,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
withResponse: [false, []],
});
break;
case ConnectorType.MODBUS:
formGroup = this.fb.group({
tag: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
type: [null, [Validators.required]],
functionCode: [null, [Validators.required]],
value: [null, []],
address: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]],
objectsCount: [null, [Validators.required, Validators.min(0), Validators.pattern(this.numbersOnlyPattern)]]
})
const valueForm = formGroup.get('value');
formGroup.get('functionCode').valueChanges.subscribe(value => {
if (value > 4) {
valueForm.addValidators([Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]);
} else {
valueForm.clearValidators();
valueForm.setValue(null);
}
valueForm.updateValueAndValidity();
})
break;
case ConnectorType.BACNET:
formGroup = this.fb.group({
method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],

28
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html

@ -41,8 +41,32 @@
</button>
</ng-container>
<ng-template #connectorForm>
<tb-gateway-service-rpc-connector formControlName="params" [connectorType]="connectorType"
(sendCommand)="sendCommand()" (saveTemplate)="saveTemplate()"/>
<tb-gateway-service-rpc-connector
*ngIf="connectorType !== ConnectorType.MODBUS else modbusParameters"
formControlName="params"
[connectorType]="connectorType"
(sendCommand)="sendCommand()"
(saveTemplate)="saveTemplate()"
/>
<ng-template #modbusParameters>
<div fxLayout="column" class="rpc-parameters">
<div class="mat-subtitle-1 tb-form-panel-title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}</div>
<tb-modbus-rpc-parameters formControlName="params"/>
<div class="template-actions" fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="10px">
<button mat-raised-button
(click)="saveTemplate()"
[disabled]="commandForm.get('params').invalid">
{{ 'gateway.rpc-command-save-template' | translate }}
</button>
<button mat-raised-button
color="primary"
(click)="sendCommand()"
[disabled]="commandForm.get('params').invalid">
{{ 'gateway.rpc-command-send' | translate }}
</button>
</div>
</div>
</ng-template>
</ng-template>
</div>
<section class="result-block" [formGroup]="commandForm">

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss

@ -40,6 +40,10 @@
}
}
.rpc-parameters {
width: 100%;
}
.result-block {
padding: 0 5px;
display: flex;

4
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts

@ -22,6 +22,7 @@ import { ContentType } from '@shared/models/constants';
import { jsonRequired } from '@shared/components/json-object-edit.component';
import {
ConnectorType,
GatewayConnectorDefaultTypesTranslatesMap,
RPCCommand,
RPCTemplate,
RPCTemplateConfig,
@ -71,6 +72,9 @@ export class GatewayServiceRPCComponent implements OnInit {
public connectorType: ConnectorType;
public templates: Array<RPCTemplate> = [];
readonly ConnectorType = ConnectorType;
readonly gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslatesMap;
private subscription: IWidgetSubscription;
private subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {

67
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts

@ -170,19 +170,27 @@ export interface ConnectorSecurity {
pathToCACert?: string;
pathToPrivateKey?: string;
pathToClientCert?: string;
mode?: ModeType;
}
export type ConnectorMapping = DeviceConnectorMapping | RequestMappingData | ConverterConnectorMapping;
export type ConnectorMappingFormValue = DeviceConnectorMapping | RequestMappingFormValue | ConverterMappingFormValue;
export type ConnectorBaseConfig = MQTTBasicConfig | OPCBasicConfig | ModbusBasicConfig;
export type ConnectorBaseConfig = ConnectorBaseInfo | MQTTBasicConfig | OPCBasicConfig | ModbusBasicConfig;
export interface ConnectorBaseInfo {
name: string;
id: string;
enableRemoteLogging: boolean;
logLevel: GatewayLogLevel;
}
export interface MQTTBasicConfig {
dataMapping: ConverterConnectorMapping[];
requestsMapping: Record<RequestType, RequestMappingData> | RequestMappingData[];
requestsMapping: Record<RequestType, RequestMappingData[]> | RequestMappingData[];
broker: BrokerConfig;
workers: WorkersConfig;
workers?: WorkersConfig;
}
export interface OPCBasicConfig {
@ -311,35 +319,15 @@ export interface RPCCommand {
time: number;
}
export enum ModbusCommandTypes {
Bits = 'bits',
Bit = 'bit',
// eslint-disable-next-line id-blacklist
String = 'string',
Bytes = 'bytes',
Int8 = '8int',
Uint8 = '8uint',
Int16 = '16int',
Uint16 = '16uint',
Float16 = '16float',
Int32 = '32int',
Uint32 = '32uint',
Float32 = '32float',
Int64 = '64int',
Uint64 = '64uint',
Float64 = '64float'
}
export const ModbusCodesTranslate = new Map<number, string>([
[1, 'gateway.rpc.read-coils'],
[2, 'gateway.rpc.read-discrete-inputs'],
[3, 'gateway.rpc.read-multiple-holding-registers'],
[4, 'gateway.rpc.read-input-registers'],
[5, 'gateway.rpc.write-single-coil'],
[6, 'gateway.rpc.write-single-holding-register'],
[15, 'gateway.rpc.write-multiple-coils'],
[16, 'gateway.rpc.write-multiple-holding-registers']
export const ModbusFunctionCodeTranslationsMap = new Map<number, string>([
[1, 'gateway.function-codes.read-coils'],
[2, 'gateway.function-codes.read-discrete-inputs'],
[3, 'gateway.function-codes.read-multiple-holding-registers'],
[4, 'gateway.function-codes.read-input-registers'],
[5, 'gateway.function-codes.write-single-coil'],
[6, 'gateway.function-codes.write-single-holding-register'],
[15, 'gateway.function-codes.write-multiple-coils'],
[16, 'gateway.function-codes.write-multiple-holding-registers']
]);
export enum BACnetRequestTypes {
@ -862,6 +850,8 @@ export enum ModbusDataType {
FLOAT64 = '64float'
}
export const ModbusEditableDataTypes = [ModbusDataType.BYTES, ModbusDataType.BITS, ModbusDataType.STRING];
export enum ModbusObjectCountByDataType {
'8int' = 1,
'8uint' = 1,
@ -920,19 +910,6 @@ export const ModbusKeysNoKeysTextTranslationsMap = new Map<ModbusValueKey, strin
]
);
export const ModbusFunctionCodeTranslationsMap = new Map<number, string>(
[
[1, 'gateway.read-coils'],
[2, 'gateway.read-discrete-inputs'],
[3, 'gateway.read-multiple-holding-registers'],
[4, 'gateway.read-input-registers'],
[5, 'gateway.write-coil'],
[6, 'gateway.write-register'],
[15, 'gateway.write-coils'],
[16, 'gateway.write-registers'],
]
);
export interface ModbusMasterConfig {
slaves: SlaveConfig[];
}

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

@ -141,6 +141,9 @@ import {
import {
TypeValuePanelComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component';
import {
ModbusRpcParametersComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component';
@NgModule({
declarations: [
@ -227,6 +230,7 @@ import {
KeyValueIsNotEmptyPipe,
ModbusBasicConfigComponent,
EllipsisChipListDirective,
ModbusRpcParametersComponent,
],
exports: [
EntitiesTableWidgetComponent,

18
ui-ngx/src/app/shared/directives/public-api.ts

@ -0,0 +1,18 @@
///
/// 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.
///
export * from './truncate-with-tooltip.directive';
export * from './ellipsis-chip-list.directive';

2
ui-ngx/src/app/shared/directives/truncate-with-tooltip.directive.ts

@ -105,8 +105,6 @@ export class TruncateWithTooltipDirective implements OnInit, AfterViewInit, OnDe
private showTooltip(): void {
this.tooltip.message = this.text;
this.renderer.setAttribute(this.elementRef.nativeElement, 'matTooltip', this.text);
this.tooltip.show();
}

1
ui-ngx/src/app/shared/public-api.ts

@ -19,3 +19,4 @@ export * from './decorators/public-api';
export * from './models/public-api';
export * from './pipe/public-api';
export * from './shared.module';
export * from './directives/public-api';

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

@ -2915,6 +2915,16 @@
"from-device-request-settings": "Input request parsing",
"from-device-request-settings-hint": "These fields support JSONPath expressions to extract a name from incoming message.",
"function-code": "Function code",
"function-codes": {
"read-coils": "01 - Read Coils",
"read-discrete-inputs": "02 - Read Discrete Inputs",
"read-multiple-holding-registers": "03 - Read Multiple Holding Registers",
"read-input-registers": "04 - Read Input Registers",
"write-single-coil": "05 - Write Single Coil",
"write-single-holding-register": "06 - Write Single Holding Register",
"write-multiple-coils": "15 - Write Multiple Coils",
"write-multiple-holding-registers": "16 - Write Multiple Holding Registers"
},
"to-device-response-settings": "Output request processing",
"to-device-response-settings-hint": "For these fields you can use the following variables and they will be replaced with actual values: ${deviceName}, ${attributeKey}, ${attributeValue}",
"gateway": "Gateway",
@ -3121,14 +3131,6 @@
"template-name-duplicate": "Template with such name already exists, it will be updated.",
"command": "Command",
"params": "Params",
"read-coils": "01: Read Coils",
"read-discrete-inputs": "02: Read Discrete Inputs",
"read-multiple-holding-registers": "03: Read Multiple Holding Registers",
"read-input-registers": "04: Read Input Registers",
"write-single-coil": "05: Write Single Coil",
"write-single-holding-register": "06: Write Single Holding Register",
"write-multiple-coils": "15: Write Multiple Coils",
"write-multiple-holding-registers": "16: Write Multiple Holding Registers",
"json-value-invalid": "JSON value has an invalid format"
},
"rpc-methods": "RPC methods",
@ -3318,16 +3320,13 @@
"username": "Username",
"username-required": "Username is required.",
"unit-id-required": "Unit ID is required.",
"read-coils": "Read Coils",
"read-discrete-inputs": "Read Discrete Inputs",
"read-multiple-holding-registers": "Read Multiple Holding Register",
"read-input-registers": "Read Input Registers",
"write-coil": "Write Coil",
"write-coils": "Write Coils",
"write-register": "Write Register",
"write-registers": "Write Registers",
"hints": {
"modbus-server": "Starting with version 3.0, Gateway can run as a Modbus slave.",
"modbus-master": "Configuration sections for connecting to Modbus servers and reading data from them.",
"modbus-server": "Configuration section for the Modbus server, storing data and sending updates to the platform when changes occur or at fixed intervals.",
"remote-configuration": "Enables remote configuration and management of the gateway",
"remote-shell": "Enables remote control of the operating system with the gateway from the Remote Shell widget",
"host": "Hostname or IP address of platform server",

Loading…
Cancel
Save