diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index fcb4e0b411..c06863c6e8 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/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, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts index f7d37b167e..d4bfdd4694 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.ts +++ b/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(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html index 9f0566b566..7e1ab2bb62 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.html @@ -18,7 +18,7 @@
-
+
{{mappingTypeTranslationsMap.get(mappingType) | translate}}
@@ -75,15 +75,22 @@ -
+ + +
+
- - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html index 17f5185bc6..105a3c0e8e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html +++ b/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 @@ +
+
{{ 'gateway.hints.modbus-server' | translate }}
+
+ + + {{ 'gateway.enable' | translate }} + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.scss index b70fe42401..3b7e7288c8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.scss +++ b/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; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts index 1fca768980..aa0424da93 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts +++ b/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; basicFormGroup: FormGroup; + enableSlaveControl: FormControl; 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}); + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html index 02d2d1491d..f53e3ee277 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html +++ b/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 @@ -
- - {{ keyControl.get('tag').value }}{{ '-' }}{{ keyControl.get('value').value }} - - {{ keyControl.get('tag').value }} +
+ {{ keyControl.get('tag').value }}{{ '-' }}{{ keyControl.get('value').value }}
+ +
+
{{ 'gateway.key' | translate }}: {{ keyControl.get('tag').value }}
+
{{ 'gateway.address' | translate }}: {{ keyControl.get('address').value }}
+
{{ 'gateway.type' | translate }}: {{ keyControl.get('type').value }}
+
+
@@ -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)" />
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss index 0e9f9a432f..445ac6c0ae 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss +++ b/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 { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts index 5b312f52c8..93ca232e38 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts +++ b/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(); 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[] { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html index e16aa0c51f..8f8aeaeef7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html +++ b/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 @@ -->
+
+
{{ 'gateway.hints.modbus-master' | translate }}
+
-
+
{{ 'gateway.servers-slaves' | translate}}
@@ -63,33 +66,40 @@ {{ 'gateway.name' | translate }} - - {{ mapping['name'] }} + + {{ slave['name'] }} {{ 'gateway.client-communication-type' | translate }} - - {{ ModbusProtocolLabelsMap.get(mapping['type']) }} + + {{ ModbusProtocolLabelsMap.get(slave['type']) }} - -
+ + +
+
- - +
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts index 2cb66f6062..4787523b40 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts +++ b/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, { + this.dialog.open(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(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.html new file mode 100644 index 0000000000..c2dead1abc --- /dev/null +++ b/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 @@ + + +
+ + {{ 'gateway.key' | translate }} + + + warning + + +
+
+ + {{ 'gateway.rpc.type' | translate }} + + {{ type }} + + + + {{ 'gateway.rpc.functionCode' | translate }} + + {{ ModbusFunctionCodeTranslationsMap.get(code) | translate}} + + +
+
+ + {{ 'gateway.rpc.value' | translate }} + + + warning + + +
+
+ + {{ 'gateway.rpc.address' | translate }} + + + warning + + + + {{ 'gateway.rpc.objectsCount' | translate }} + + +
+
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.ts new file mode 100644 index 0000000000..17c48cc595 --- /dev/null +++ b/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; + + 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(); + + 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}); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html index d37c8cc9ee..d1a16c33bc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html +++ b/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 @@ -->
-
-
{{ 'gateway.hints.modbus-server' | translate }}
-
- - - {{ 'gateway.enable' | translate }} - - -
-
gateway.server-slave-config
@@ -161,7 +151,7 @@
-
gateway.poll-period
+
gateway.poll-period
@@ -178,6 +168,13 @@
+
+ + + {{ 'gateway.send-data-TB' | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.scss deleted file mode 100644 index a464832202..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.scss +++ /dev/null @@ -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; - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts index 2a8488a2d4..5f5b4d293c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts +++ b/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 })); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html index 1e47479f03..088b385623 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html +++ b/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 @@
-
gateway.name
+
gateway.name
@@ -306,7 +306,7 @@
-
gateway.poll-period
+
gateway.poll-period
@@ -314,7 +314,7 @@
-
gateway.connect-attempt-time
+
gateway.connect-attempt-time
@@ -322,7 +322,7 @@
-
gateway.connect-attempt-count
+
gateway.connect-attempt-count
@@ -330,7 +330,7 @@
-
gateway.wait-after-failed-attempts
+
gateway.wait-after-failed-attempts
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.scss index 8cea184957..8900741a93 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.scss +++ b/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); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts index a00a488aa0..e21a9921a7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts +++ b/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 void; + private onChange: (value: MQTTBasicConfig) => void; private onTouched: () => void; private destroy$ = new Subject(); @@ -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): RequestMappingData[] { + private getRequestDataArray(value: Record): RequestMappingData[] { const mappingConfigs = []; if (isObject(value)) { @@ -157,4 +175,17 @@ export class MqttBasicConfigComponent implements ControlValueAccessor, Validator return mappingConfigs; } + + private getRequestDataObject(array: RequestMappingData[]): Record { + return array.reduce((result, { requestType, requestValue }) => { + result[requestType].push(requestValue); + return result; + }, { + connectRequests: [], + disconnectRequests: [], + attributeRequests: [], + attributeUpdates: [], + serverSideRpc: [], + }); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts index eddad255a3..ff50480b92 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts +++ b/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 }); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index e4c5360cdf..a91a7c1b9f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/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 { 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): void { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html index eb428bebdc..3e94ae96d7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html @@ -50,46 +50,6 @@ placeholder="${params}"/> - - - {{ 'gateway.rpc.tag' | translate }} - - -
- - {{ 'gateway.rpc.type' | translate }} - - - {{ type }} - - - - - {{ 'gateway.rpc.functionCode' | translate }} - - - {{ modbusCodesTranslate.get(code) | translate}} - - - -
- - {{ 'gateway.rpc.value' | translate }} - - -
- - {{ 'gateway.rpc.address' | translate }} - - - - {{ 'gateway.rpc.objectsCount' | translate }} - - -
-
{{ 'gateway.rpc.methodRPC' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index 3027cafa8d..989f9daeed 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/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 = new EventEmitter(); commandForm: FormGroup; - codesArray: Array = [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)]], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html index 8368698087..41dea0b656 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html @@ -41,8 +41,32 @@ - + + +
+
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
+ +
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss index f74097bbba..b8424dc068 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss +++ b/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; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index 1f3327ec06..d6b20d0137 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/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 = []; + readonly ConnectorType = ConnectorType; + readonly gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslatesMap; + private subscription: IWidgetSubscription; private subscriptionOptions: WidgetSubscriptionOptions = { callbacks: { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 8df3994ea2..fe6d2d6d82 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/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 | RequestMappingData[]; + requestsMapping: Record | 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([ - [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([ + [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( - [ - [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[]; } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 51d82dece9..db6b7543a4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/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, diff --git a/ui-ngx/src/app/shared/directives/public-api.ts b/ui-ngx/src/app/shared/directives/public-api.ts new file mode 100644 index 0000000000..6f25320d90 --- /dev/null +++ b/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'; diff --git a/ui-ngx/src/app/shared/directives/truncate-with-tooltip.directive.ts b/ui-ngx/src/app/shared/directives/truncate-with-tooltip.directive.ts index 1281b86040..ad4e4cbbc1 100644 --- a/ui-ngx/src/app/shared/directives/truncate-with-tooltip.directive.ts +++ b/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(); } diff --git a/ui-ngx/src/app/shared/public-api.ts b/ui-ngx/src/app/shared/public-api.ts index bd3d565da3..1f38b7f70c 100644 --- a/ui-ngx/src/app/shared/public-api.ts +++ b/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'; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e899ef7ace..b32818723b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/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",