diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts index b38a07cd0f..8b05572659 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts @@ -16,10 +16,12 @@ import { GatewayConnector, + LegacySlaveConfig, ModbusBasicConfig, ModbusBasicConfig_v3_5_2, ModbusLegacyBasicConfig, ModbusLegacySlave, + ModbusMasterConfig, ModbusSlave, } from '../gateway-widget.models'; import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; @@ -40,7 +42,7 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor) : { slaves: [] }, slave: configurationJson.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave) @@ -59,7 +61,9 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts index f3daa214dd..66024c6a8c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts @@ -25,8 +25,8 @@ import { } from '@home/components/widget/lib/gateway/gateway-widget.models'; @Directive() -export abstract class ModbusBasicConfigDirective - extends GatewayConnectorBasicConfigDirective { +export abstract class ModbusBasicConfigDirective + extends GatewayConnectorBasicConfigDirective { enableSlaveControl: FormControl = new FormControl(false); @@ -41,7 +41,7 @@ export abstract class ModbusBasicConfigDirective }); } - override writeValue(basicConfig: BasicConfig & ModbusBasicConfig): void { + override writeValue(basicConfig: OutputBasicConfig & ModbusBasicConfig): void { super.writeValue(basicConfig); this.onEnableSlaveControl(basicConfig); } 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 30233cddcf..76098288b8 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 @@ -20,7 +20,7 @@ - +
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 b5fab7c92d..dceffd3f7d 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 @@ -56,7 +56,9 @@ import { ], styleUrls: ['./modbus-basic-config.component.scss'], }) -export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective { +export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective { + + isLegacy = false; protected override mapConfigToFormValue({ master, slave }: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { return { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts index 5bca9b0292..c10214d57b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts @@ -17,8 +17,10 @@ import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { - ModbusBasicConfig_v3_5_2, - ModbusLegacyBasicConfig, ModbusLegacySlave, + LegacySlaveConfig, + ModbusBasicConfig, + ModbusLegacyBasicConfig, + ModbusLegacySlave, ModbusMasterConfig, ModbusSlave } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -58,21 +60,21 @@ import { ], styleUrls: ['./modbus-basic-config.component.scss'], }) -export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective { +export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective { - protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 { + isLegacy = true; + + protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig { return { - master: config.master?.slaves - ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master) - : { slaves: [] } as ModbusMasterConfig, - slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave, - }; + master: config.master?.slaves ? config.master : { slaves: [] } as ModbusMasterConfig, + slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave as ModbusLegacySlave) : {}, + } as ModbusBasicConfig; } - protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusLegacyBasicConfig { + protected override getMappedValue(value: ModbusBasicConfig): ModbusLegacyBasicConfig { return { - master: value.master, - slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave) : {} as ModbusLegacySlave, + master: value.master as ModbusMasterConfig, + slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave as ModbusSlave) : {} as ModbusLegacySlave, }; } } 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 fc94fe49ea..092621b178 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 @@ -165,6 +165,11 @@
+ 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 3c61d351b7..f158efbad4 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 @@ -41,6 +41,9 @@ import { generateSecret } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; +import { + ReportStrategyComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; @Component({ selector: 'tb-modbus-data-keys-panel', @@ -51,12 +54,15 @@ import { Subject } from 'rxjs'; CommonModule, SharedModule, GatewayHelpLinkPipe, + ReportStrategyComponent, ] }) export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { @coerceBoolean() @Input() isMaster = false; + @coerceBoolean() + @Input() hideNewFields = false; @Input() panelTitle: string; @Input() addKeyTitle: string; @Input() deleteKeyTitle: string; @@ -70,6 +76,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { keysListFormArray: FormArray; modbusDataTypes = Object.values(ModbusDataType); withFunctionCode = true; + withReportStrategy = true; functionCodesMap = new Map(); defaultFunctionCodes = []; @@ -87,6 +94,9 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { ngOnInit(): void { this.withFunctionCode = !this.isMaster || (this.keysType !== ModbusValueKey.ATTRIBUTES && this.keysType !== ModbusValueKey.TIMESERIES); + this.withReportStrategy = !this.isMaster + && (this.keysType === ModbusValueKey.ATTRIBUTES || this.keysType === ModbusValueKey.TIMESERIES) + && !this.hideNewFields; this.keysListFormArray = this.prepareKeysFormArray(this.values); this.defaultFunctionCodes = this.getDefaultFunctionCodes(); } @@ -108,6 +118,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { address: [null, [Validators.required]], objectsCount: [1, [Validators.required]], functionCode: [{ value: this.getDefaultFunctionCodes()[0], disabled: !this.withFunctionCode }, [Validators.required]], + reportStrategy: [{ value: null, disabled: !this.withReportStrategy }], id: [{value: generateSecret(5), disabled: true}], }); this.observeKeyDataType(dataKeyFormGroup); @@ -128,7 +139,20 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { } applyKeysData(): void { - this.keysDataApplied.emit(this.keysListFormArray.value); + this.keysDataApplied.emit(this.getFormValue()); + } + + private getFormValue(): ModbusValue[] { + return this.withReportStrategy + ? this.cleanUpEmptyStrategies(this.keysListFormArray.value) + : this.keysListFormArray.value; + } + + private cleanUpEmptyStrategies(values: ModbusValue[]): ModbusValue[] { + return values.map((key) => { + const { reportStrategy, ...updatedKey } = key; + return !reportStrategy ? updatedKey : key; + }); } private prepareKeysFormArray(values: ModbusValue[]): UntypedFormArray { @@ -148,7 +172,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { } private createDataKeyFormGroup(modbusValue: ModbusValue): FormGroup { - const { tag, value, type, address, objectsCount, functionCode } = modbusValue; + const { tag, value, type, address, objectsCount, functionCode, reportStrategy } = modbusValue; return this.fb.group({ tag: [tag, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], @@ -158,6 +182,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { objectsCount: [objectsCount, [Validators.required]], functionCode: [{ value: functionCode, disabled: !this.withFunctionCode }, [Validators.required]], id: [{ value: generateSecret(5), disabled: true }], + reportStrategy: [{ value: reportStrategy, disabled: !this.withReportStrategy }], }); } 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 4787523b40..8f1761dd87 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 @@ -21,12 +21,13 @@ import { Component, ElementRef, forwardRef, + Input, OnDestroy, OnInit, ViewChild, } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; import { Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged, take, takeUntil } from 'rxjs/operators'; @@ -38,8 +39,9 @@ import { UntypedFormGroup, } from '@angular/forms'; import { + LegacySlaveConfig, ModbusMasterConfig, - ModbusProtocolLabelsMap, + ModbusProtocolLabelsMap, ModbusSlave, ModbusSlaveInfo, ModbusValues, SlaveConfig @@ -49,6 +51,10 @@ import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { ModbusSlaveDialogComponent } from '../modbus-slave-dialog/modbus-slave-dialog.component'; import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { + ModbusLegacySlaveDialogComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component'; @Component({ selector: 'tb-modbus-master-table', @@ -69,6 +75,9 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi @ViewChild('searchInput') searchInputField: ElementRef; + @coerceBoolean() + @Input() isLegacy = false; + textSearchMode = false; dataSource: SlavesDatasource; masterFormGroup: UntypedFormGroup; @@ -152,14 +161,7 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi } const withIndex = isDefinedAndNotNull(index); const value = withIndex ? this.slaves.at(index).value : {}; - this.dialog.open(ModbusSlaveDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - value, - buttonTitle: withIndex ? 'action.apply' : 'action.add' - } - }).afterClosed() + this.getSlaveDialog(value, withIndex ? 'action.apply' : 'action.add').afterClosed() .pipe(take(1), takeUntil(this.destroy$)) .subscribe(res => { if (res) { @@ -173,6 +175,33 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi }); } + private getSlaveDialog( + value: LegacySlaveConfig | SlaveConfig, + buttonTitle: string + ): MatDialogRef { + if (this.isLegacy) { + return this.dialog.open, ModbusValues> + (ModbusLegacySlaveDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + value: value as LegacySlaveConfig, + hideNewFields: true, + buttonTitle + } + }); + } + return this.dialog.open(ModbusSlaveDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + value: value as SlaveConfig, + buttonTitle, + hideNewFields: false, + } + }); + } + 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-slave-dialog/modbus-legacy-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts new file mode 100644 index 0000000000..137ef385ab --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts @@ -0,0 +1,84 @@ +/// +/// 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, Inject } from '@angular/core'; +import { + FormBuilder, +} from '@angular/forms'; +import { + LegacySlaveConfig, + ModbusProtocolType, + ModbusSlaveInfo, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { ModbusValuesComponent } from '../modbus-values/modbus-values.component'; +import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; +import { + ReportStrategyComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; +import { + ModbusSlaveDialogAbstract +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract'; + +@Component({ + selector: 'tb-modbus-legacy-slave-dialog', + templateUrl: './modbus-slave-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusValuesComponent, + ModbusSecurityConfigComponent, + GatewayPortTooltipPipe, + ReportStrategyComponent, + ], + styleUrls: ['./modbus-slave-dialog.component.scss'], +}) +export class ModbusLegacySlaveDialogComponent extends ModbusSlaveDialogAbstract { + + constructor( + protected fb: FormBuilder, + protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, + public dialogRef: MatDialogRef, + ) { + super(fb, store, router, data, dialogRef); + } + + protected override getSlaveResultData(): LegacySlaveConfig { + const { values, type, serialPort, ...rest } = this.slaveConfigFormGroup.value; + const slaveResult = { ...rest, type, ...values }; + + if (type === ModbusProtocolType.Serial) { + slaveResult.port = serialPort; + } + + return slaveResult; + } + + + protected override addFieldsToFormGroup(): void { + this.slaveConfigFormGroup.addControl('sendDataOnlyOnChange', this.fb.control(false)); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts new file mode 100644 index 0000000000..6d434591d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts @@ -0,0 +1,207 @@ +/// +/// 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 { Directive, Inject, OnDestroy } from '@angular/core'; +import { + FormBuilder, + FormControl, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { + ModbusBaudrates, + ModbusByteSizes, + ModbusMethodLabelsMap, + ModbusMethodType, + ModbusOrderType, + ModbusParity, + ModbusParityLabelsMap, + ModbusProtocolLabelsMap, + ModbusProtocolType, + ModbusSerialMethodType, + ModbusSlaveInfo, + noLeadTrailSpacesRegex, + PortLimits, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { Subject } from 'rxjs'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { takeUntil } from 'rxjs/operators'; +import { isEqual } from '@core/utils'; +import { helpBaseUrl } from '@shared/models/constants'; + +@Directive() +export abstract class ModbusSlaveDialogAbstract extends DialogComponent implements OnDestroy { + + slaveConfigFormGroup: UntypedFormGroup; + showSecurityControl: FormControl; + portLimits = PortLimits; + + readonly modbusProtocolTypes = Object.values(ModbusProtocolType); + readonly modbusMethodTypes = Object.values(ModbusMethodType); + readonly modbusSerialMethodTypes = Object.values(ModbusSerialMethodType); + readonly modbusParities = Object.values(ModbusParity); + readonly modbusByteSizes = ModbusByteSizes; + readonly modbusBaudrates = ModbusBaudrates; + readonly modbusOrderType = Object.values(ModbusOrderType); + readonly ModbusProtocolType = ModbusProtocolType; + readonly ModbusParityLabelsMap = ModbusParityLabelsMap; + readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; + readonly ModbusMethodLabelsMap = ModbusMethodLabelsMap; + readonly modbusHelpLink = + helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters'; + + private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict']; + private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host']; + + private destroy$ = new Subject(); + + constructor( + protected fb: FormBuilder, + protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, + public dialogRef: MatDialogRef, + ) { + super(store, router, dialogRef); + + this.showSecurityControl = this.fb.control(false); + this.initializeSlaveFormGroup(); + this.updateSlaveFormGroup(); + this.updateControlsEnabling(this.data.value.type); + this.observeTypeChange(); + this.observeShowSecurity(); + this.showSecurityControl.patchValue(!!this.data.value.security && !isEqual(this.data.value.security, {})); + } + + get protocolType(): ModbusProtocolType { + return this.slaveConfigFormGroup.get('type').value; + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + if (!this.slaveConfigFormGroup.valid) { + return; + } + + this.dialogRef.close(this.getSlaveResultData()); + } + + private initializeSlaveFormGroup(): void { + this.slaveConfigFormGroup = this.fb.group({ + name: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + type: [ModbusProtocolType.TCP], + host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], + serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + method: [ModbusMethodType.SOCKET, [Validators.required]], + baudrate: [this.modbusBaudrates[0]], + stopbits: [1], + bytesize: [ModbusByteSizes[0]], + parity: [ModbusParity.None], + strict: [true], + unitId: [null, [Validators.required]], + deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + timeout: [35], + byteOrder: [ModbusOrderType.BIG], + wordOrder: [ModbusOrderType.BIG], + retries: [true], + retryOnEmpty: [true], + retryOnInvalid: [true], + pollPeriod: [5000, [Validators.required]], + connectAttemptTimeMs: [5000, [Validators.required]], + connectAttemptCount: [5, [Validators.required]], + waitAfterFailedAttemptsMs: [300000, [Validators.required]], + values: [{}], + security: [{}], + }); + this.addFieldsToFormGroup(); + } + + private updateSlaveFormGroup(): void { + this.slaveConfigFormGroup.patchValue({ + ...this.data.value, + port: this.data.value.type === ModbusProtocolType.Serial ? null : this.data.value.port, + serialPort: this.data.value.type === ModbusProtocolType.Serial ? this.data.value.port : '', + values: { + attributes: this.data.value.attributes ?? [], + timeseries: this.data.value.timeseries ?? [], + attributeUpdates: this.data.value.attributeUpdates ?? [], + rpc: this.data.value.rpc ?? [], + } + }); + } + + private observeTypeChange(): void { + this.slaveConfigFormGroup.get('type').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(type => { + this.updateControlsEnabling(type); + this.updateMethodType(type); + }); + } + + private updateMethodType(type: ModbusProtocolType): void { + if (this.slaveConfigFormGroup.get('method').value !== ModbusMethodType.RTU) { + this.slaveConfigFormGroup.get('method').patchValue( + type === ModbusProtocolType.Serial + ? ModbusSerialMethodType.ASCII + : ModbusMethodType.SOCKET, + {emitEvent: false} + ); + } + } + + private updateControlsEnabling(type: ModbusProtocolType): void { + const [enableKeys, disableKeys] = type === ModbusProtocolType.Serial + ? [this.serialSpecificControlKeys, this.tcpUdpSpecificControlKeys] + : [this.tcpUdpSpecificControlKeys, this.serialSpecificControlKeys]; + + enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false })); + disableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({ emitEvent: false })); + + this.updateSecurityEnabling(this.showSecurityControl.value); + } + + private observeShowSecurity(): void { + this.showSecurityControl.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => this.updateSecurityEnabling(value)); + } + + private updateSecurityEnabling(isEnabled: boolean): void { + if (isEnabled && this.protocolType !== ModbusProtocolType.Serial) { + this.slaveConfigFormGroup.get('security').enable({emitEvent: false}); + } else { + this.slaveConfigFormGroup.get('security').disable({emitEvent: false}); + } + } + + protected abstract addFieldsToFormGroup(): void; + protected abstract getSlaveResultData(): Config; +} 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 6304cdc0cb..d869785589 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 @@ -227,13 +227,16 @@ -
+
{{ 'gateway.send-data-on-change' | translate }}
+ + +
@@ -345,7 +348,7 @@
- +
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 0167eaafb7..d9a9c426c2 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 @@ -14,62 +14,35 @@ /// limitations under the License. /// -import { ChangeDetectionStrategy, Component, forwardRef, Inject, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { FormBuilder, - FormControl, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - Validators, } from '@angular/forms'; import { - ModbusBaudrates, - ModbusByteSizes, - ModbusMethodLabelsMap, - ModbusMethodType, - ModbusOrderType, - ModbusParity, - ModbusParityLabelsMap, - ModbusProtocolLabelsMap, ModbusProtocolType, - ModbusSerialMethodType, ModbusSlaveInfo, - noLeadTrailSpacesRegex, - PortLimits, SlaveConfig, } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; -import { Subject } from 'rxjs'; import { ModbusValuesComponent } from '../modbus-values/modbus-values.component'; import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; -import { DialogComponent } from '@shared/components/dialog.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; -import { takeUntil } from 'rxjs/operators'; -import { isEqual } from '@core/utils'; -import { helpBaseUrl } from '@shared/models/constants'; +import { + ReportStrategyComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; +import { + ModbusSlaveDialogAbstract +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract'; @Component({ selector: 'tb-modbus-slave-dialog', templateUrl: './modbus-slave-dialog.component.html', changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusSlaveDialogComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ModbusSlaveDialogComponent), - multi: true - } - ], standalone: true, imports: [ CommonModule, @@ -77,70 +50,23 @@ import { helpBaseUrl } from '@shared/models/constants'; ModbusValuesComponent, ModbusSecurityConfigComponent, GatewayPortTooltipPipe, + ReportStrategyComponent, ], styleUrls: ['./modbus-slave-dialog.component.scss'], }) -export class ModbusSlaveDialogComponent extends DialogComponent implements OnDestroy { - - slaveConfigFormGroup: UntypedFormGroup; - showSecurityControl: FormControl; - portLimits = PortLimits; - - readonly modbusProtocolTypes = Object.values(ModbusProtocolType); - readonly modbusMethodTypes = Object.values(ModbusMethodType); - readonly modbusSerialMethodTypes = Object.values(ModbusSerialMethodType); - readonly modbusParities = Object.values(ModbusParity); - readonly modbusByteSizes = ModbusByteSizes; - readonly modbusBaudrates = ModbusBaudrates; - readonly modbusOrderType = Object.values(ModbusOrderType); - readonly ModbusProtocolType = ModbusProtocolType; - readonly ModbusParityLabelsMap = ModbusParityLabelsMap; - readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; - readonly ModbusMethodLabelsMap = ModbusMethodLabelsMap; - readonly modbusHelpLink = - helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters'; - - private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict']; - private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host']; - - private destroy$ = new Subject(); +export class ModbusSlaveDialogComponent extends ModbusSlaveDialogAbstract { constructor( - private fb: FormBuilder, + protected fb: FormBuilder, protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, public dialogRef: MatDialogRef, ) { - super(store, router, dialogRef); - - this.showSecurityControl = this.fb.control(false); - this.initializeSlaveFormGroup(); - this.updateSlaveFormGroup(); - this.updateControlsEnabling(this.data.value.type); - this.observeTypeChange(); - this.observeShowSecurity(); - this.showSecurityControl.patchValue(!!this.data.value.security && !isEqual(this.data.value.security, {})); + super(fb, store, router, data, dialogRef); } - get protocolType(): ModbusProtocolType { - return this.slaveConfigFormGroup.get('type').value; - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - cancel(): void { - this.dialogRef.close(null); - } - - add(): void { - if (!this.slaveConfigFormGroup.valid) { - return; - } - + protected override getSlaveResultData(): SlaveConfig { const { values, type, serialPort, ...rest } = this.slaveConfigFormGroup.value; const slaveResult = { ...rest, type, ...values }; @@ -148,97 +74,14 @@ export class ModbusSlaveDialogComponent extends DialogComponent { - this.updateControlsEnabling(type); - this.updateMethodType(type); - }); - } - - private updateMethodType(type: ModbusProtocolType): void { - if (this.slaveConfigFormGroup.get('method').value !== ModbusMethodType.RTU) { - this.slaveConfigFormGroup.get('method').patchValue( - type === ModbusProtocolType.Serial - ? ModbusSerialMethodType.ASCII - : ModbusMethodType.SOCKET, - {emitEvent: false} - ); + if (!slaveResult.reportStrategy) { + delete slaveResult.reportStrategy; } + + return slaveResult; } - private updateControlsEnabling(type: ModbusProtocolType): void { - const [enableKeys, disableKeys] = type === ModbusProtocolType.Serial - ? [this.serialSpecificControlKeys, this.tcpUdpSpecificControlKeys] - : [this.tcpUdpSpecificControlKeys, this.serialSpecificControlKeys]; - - enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false })); - disableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({ emitEvent: false })); - - this.updateSecurityEnabling(this.showSecurityControl.value); - } - - private observeShowSecurity(): void { - this.showSecurityControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => this.updateSecurityEnabling(value)); - } - - private updateSecurityEnabling(isEnabled: boolean): void { - if (isEnabled && this.protocolType !== ModbusProtocolType.Serial) { - this.slaveConfigFormGroup.get('security').enable({emitEvent: false}); - } else { - this.slaveConfigFormGroup.get('security').disable({emitEvent: false}); - } + protected override addFieldsToFormGroup(): void { + this.slaveConfigFormGroup.addControl('reportStrategy', this.fb.control(null)); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts index 6bfc07c130..c0824869e3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts @@ -87,6 +87,9 @@ export class ModbusValuesComponent implements ControlValueAccessor, Validator, O @coerceBoolean() @Input() singleMode = false; + @coerceBoolean() + @Input() hideNewFields = false; + disabled = false; modbusRegisterTypes: ModbusRegisterType[] = Object.values(ModbusRegisterType); modbusValueKeys = Object.values(ModbusValueKey); @@ -172,7 +175,8 @@ export class ModbusValuesComponent implements ControlValueAccessor, Validator, O panelTitle: ModbusKeysPanelTitleTranslationsMap.get(keysType), addKeyTitle: ModbusKeysAddKeyTranslationsMap.get(keysType), deleteKeyTitle: ModbusKeysDeleteKeyTranslationsMap.get(keysType), - noKeysText: ModbusKeysNoKeysTextTranslationsMap.get(keysType) + noKeysText: ModbusKeysNoKeysTextTranslationsMap.get(keysType), + hideNewFields: this.hideNewFields, }; const dataKeysPanelPopover = this.popoverService.displayPopover( trigger, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html new file mode 100644 index 0000000000..dec8292720 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html @@ -0,0 +1,57 @@ + +
+ + + + + + {{ 'gateway.report-strategy.label' | translate }} + + + + + + + +
gateway.report-strategy.label
+ +
+ +
+
{{ 'gateway.type' | translate }}
+ + + {{ ReportTypeTranslateMap.get(type) | translate }} + + +
+
+
+ + gateway.report-strategy.report-period + +
+
+ + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts new file mode 100644 index 0000000000..a4320f4a3d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts @@ -0,0 +1,168 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + Component, + forwardRef, + Input, + OnDestroy, +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + ValidationErrors, + Validators +} from '@angular/forms'; +import { + ReportStrategyConfig, + ReportStrategyType, + ReportStrategyTypeTranslationsMap +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { filter, takeUntil } from 'rxjs/operators'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { + ModbusSecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component'; +import { coerceBoolean } from '@shared/decorators/coercion'; + +@Component({ + selector: 'tb-report-strategy', + templateUrl: './report-strategy.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ReportStrategyComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ReportStrategyComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusSecurityConfigComponent, + ] +}) +export class ReportStrategyComponent implements ControlValueAccessor, OnDestroy { + + @coerceBoolean() + @Input() isExpansionMode = false; + + reportStrategyFormGroup: UntypedFormGroup; + showStrategyControl: FormControl; + + readonly reportStrategyTypes = Object.values(ReportStrategyType); + readonly ReportTypeTranslateMap = ReportStrategyTypeTranslationsMap; + readonly ReportStrategyType = ReportStrategyType; + + private onChange: (value: ReportStrategyConfig) => void; + private onTouched: () => void; + + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder) { + this.showStrategyControl = this.fb.control(false); + + this.reportStrategyFormGroup = this.fb.group({ + type: [ReportStrategyType.OnReportPeriod, []], + reportPeriod: [5000, [Validators.required]], + }); + + this.observeStrategyFormChange(); + this.observeStrategyToggle(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + writeValue(reportStrategyConfig: ReportStrategyConfig): void { + if (this.isExpansionMode) { + this.showStrategyControl.setValue(!!reportStrategyConfig, {emitEvent: false}); + } + const { type = ReportStrategyType.OnReportPeriod, reportPeriod = 5000 } = reportStrategyConfig ?? {}; + this.reportStrategyFormGroup.setValue({ type, reportPeriod }, {emitEvent: false}); + this.onTypeChange(type); + } + + validate(): ValidationErrors | null { + return this.reportStrategyFormGroup.valid || this.reportStrategyFormGroup.disabled ? null : { + reportStrategyForm: { valid: false } + }; + } + + registerOnChange(fn: (value: ReportStrategyConfig) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + private observeStrategyFormChange(): void { + this.reportStrategyFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.onChange(value); + this.onTouched(); + }); + + this.reportStrategyFormGroup.get('type').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(type => this.onTypeChange(type)); + } + + private observeStrategyToggle(): void { + this.showStrategyControl.valueChanges + .pipe(takeUntil(this.destroy$), filter(() => this.isExpansionMode)) + .subscribe(enable => { + if (enable) { + this.reportStrategyFormGroup.enable({emitEvent: false}); + this.reportStrategyFormGroup.get('reportPeriod').addValidators(Validators.required); + this.onChange(this.reportStrategyFormGroup.value); + } else { + this.reportStrategyFormGroup.disable({emitEvent: false}); + this.reportStrategyFormGroup.get('reportPeriod').removeValidators(Validators.required); + this.onChange(null); + } + this.reportStrategyFormGroup.updateValueAndValidity({emitEvent: false}); + }); + } + + private onTypeChange(type: ReportStrategyType): void { + const reportPeriodControl = this.reportStrategyFormGroup.get('reportPeriod'); + const isDisabled = reportPeriodControl.disabled; + + if (type === ReportStrategyType.OnChange && !isDisabled) { + reportPeriodControl.disable({emitEvent: false}); + } else if (isDisabled) { + reportPeriodControl.enable({emitEvent: false}); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 7c495d34f1..6a054a4000 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -299,5 +299,6 @@ + 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 04dd274f81..dcc473ce71 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 @@ -297,12 +297,13 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } private hasSameConfig(sharedDataConfigJson: ConnectorBaseInfo, connectorDataConfigJson: ConnectorBaseInfo): boolean { - const { name, id, enableRemoteLogging, logLevel, ...sharedDataConfig } = sharedDataConfigJson; + const { name, id, enableRemoteLogging, logLevel, reportStrategy, ...sharedDataConfig } = sharedDataConfigJson; const { name: connectorName, id: connectorId, enableRemoteLogging: connectorEnableRemoteLogging, logLevel: connectorLogLevel, + reportStrategy: connectorReportStrategy, ...connectorConfig } = connectorDataConfigJson; @@ -351,7 +352,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie configuration: '', configurationJson: {}, basicConfig: {}, - configVersion: '' + configVersion: '', + reportStrategy: [{ value: {}, disabled: true }], }, {emitEvent: false}); this.connectorForm.markAsPristine(); } @@ -542,6 +544,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie configurationJson: [{}, [Validators.required]], basicConfig: [{}], configVersion: [''], + reportStrategy: [{ value: {}, disabled: true }], }); this.connectorForm.disable(); } @@ -751,6 +754,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } private updateConnector(connector: GatewayConnector): void { + this.toggleReportStrategy(connector.type); switch (connector.type) { case ConnectorType.MQTT: case ConnectorType.OPCUA: @@ -770,6 +774,15 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.createJsonConfigWatcher(); } + private toggleReportStrategy(type: ConnectorType): void { + const reportStrategyControl = this.connectorForm.get('reportStrategy'); + if (type === ConnectorType.MODBUS) { + reportStrategyControl.enable({emitEvent: false}); + } else { + reportStrategyControl.disable({emitEvent: false}); + } + } + private setClientData(data: PageData): void { if (this.initialConnector) { const clientConnectorData = data.data.find(attr => attr.key === this.initialConnector.name); 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 a2184089b1..45f8d3cc54 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 @@ -208,6 +208,7 @@ export interface ConnectorBaseInfo { id: string; enableRemoteLogging: boolean; logLevel: GatewayLogLevel; + reportStrategy?: ReportStrategyConfig; } export type MQTTBasicConfig = MQTTBasicConfig_v3_5_2 | MQTTLegacyBasicConfig; @@ -254,7 +255,7 @@ export interface ModbusBasicConfig_v3_5_2 { } export interface ModbusLegacyBasicConfig { - master: ModbusMasterConfig; + master: ModbusMasterConfig; slave: ModbusLegacySlave; } @@ -588,9 +589,10 @@ export interface MappingInfo { buttonTitle: string; } -export interface ModbusSlaveInfo { - value: SlaveConfig; +export interface ModbusSlaveInfo { + value: Slave; buttonTitle: string; + hideNewFields: boolean; } export enum ConfigurationModes { @@ -604,6 +606,20 @@ export enum SecurityType { CERTIFICATES = 'certificates' } +export enum ReportStrategyType { + OnChange = 'ON_CHANGE', + OnReportPeriod = 'ON_REPORT_PERIOD', + OnChangeOrReportPeriod = 'ON_CHANGE_OR_REPORT_PERIOD' +} + +export const ReportStrategyTypeTranslationsMap = new Map( + [ + [ReportStrategyType.OnChange, 'gateway.report-strategy.on-change'], + [ReportStrategyType.OnReportPeriod, 'gateway.report-strategy.on-report-period'], + [ReportStrategyType.OnChangeOrReportPeriod, 'gateway.report-strategy.on-change-or-report-period'] + ] +); + export enum ModeType { NONE = 'None', SIGN = 'Sign', @@ -1097,8 +1113,12 @@ export const ModbusKeysNoKeysTextTranslationsMap = new Map { + slaves: Slave[]; +} + +export interface LegacySlaveConfig extends Omit { + sendDataOnlyOnChange: boolean; } export interface SlaveConfig { @@ -1118,7 +1138,7 @@ export interface SlaveConfig { unitId: number; deviceName: string; deviceType?: string; - sendDataOnlyOnChange: boolean; + reportStrategy: ReportStrategyConfig; connectAttemptTimeMs: number; connectAttemptCount: number; waitAfterFailedAttemptsMs: number; @@ -1141,6 +1161,7 @@ export interface ModbusValue { objectsCount: number; address: number; value?: string; + reportStrategy?: ReportStrategyConfig; } export interface ModbusSecurity { @@ -1205,4 +1226,9 @@ export interface ModbusIdentity { modelName?: string; } +export interface ReportStrategyConfig { + type: ReportStrategyType; + reportPeriod?: number; +} + export const ModbusBaudrates = [4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600]; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts index 3f34d49ab2..eb1ba9033f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts @@ -15,6 +15,7 @@ /// import { + LegacySlaveConfig, ModbusDataType, ModbusLegacyRegisterValues, ModbusLegacySlave, @@ -23,17 +24,36 @@ import { ModbusSlave, ModbusValue, ModbusValues, + ReportStrategyType, SlaveConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; export class ModbusVersionMappingUtil { - static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { + static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { return { - slaves: master.slaves.map((slave: SlaveConfig) => ({ - ...slave, - deviceType: slave.deviceType ?? 'default', - })) + slaves: master.slaves.map((slave: LegacySlaveConfig) => { + const { sendDataOnlyOnChange, ...restSlave } = slave; + return { + ...restSlave, + deviceType: slave.deviceType ?? 'default', + reportStrategy: sendDataOnlyOnChange + ? { type: ReportStrategyType.OnChange } + : { type: ReportStrategyType.OnReportPeriod, reportPeriod: slave.pollPeriod } + }; + }) + }; + } + + static mapMasterToDowngradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { + return { + slaves: master.slaves.map((slave: SlaveConfig) => { + const { reportStrategy, ...restSlave } = slave; + return { + ...restSlave, + sendDataOnlyOnChange: reportStrategy?.type !== ReportStrategyType.OnReportPeriod + }; + }) }; } 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 f241bc91d0..e809c22b25 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 @@ -160,6 +160,9 @@ import { import { ModbusLegacyBasicConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component'; +import { + ReportStrategyComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; @NgModule({ declarations: [ @@ -253,6 +256,7 @@ import { GatewayAdvancedConfigurationComponent, OpcUaLegacyBasicConfigComponent, ModbusLegacyBasicConfigComponent, + ReportStrategyComponent, ], exports: [ EntitiesTableWidgetComponent, 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 f5f26beeeb..a06896e9e9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3348,6 +3348,13 @@ "memory-storage": "Memory storage", "sqlite": "SQLITE" }, + "report-strategy": { + "label": "Report strategy", + "on-change": "On value change", + "on-report-period": "On report period", + "on-change-or-report-period": "On value change or report period", + "report-period": "Report period" + }, "source-type": { "msg": "Extract from message", "topic": "Extract from topic", diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json index 38bb44e036..fbace65d78 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json @@ -18,7 +18,6 @@ "unitId": 1, "deviceName": "Temp Sensor", "deviceType": "default", - "sendDataOnlyOnChange": true, "connectAttemptTimeMs": 5000, "connectAttemptCount": 5, "waitAfterFailedAttemptsMs": 300000,