Added Modbus Report Strategy

This commit is contained in:
mpetrov 2024-09-18 15:33:43 +03:00
parent fb54dbc858
commit 1b36e5770a
22 changed files with 726 additions and 223 deletions

View File

@ -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<any
...this.connector,
configurationJson: {
master: configurationJson.master?.slaves
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master)
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master as ModbusMasterConfig<LegacySlaveConfig>)
: { slaves: [] },
slave: configurationJson.slave
? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave)
@ -59,7 +61,9 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor<any
slave: configurationJson.slave
? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(configurationJson.slave as ModbusSlave)
: {} as ModbusLegacySlave,
master: configurationJson.master,
master: configurationJson.master?.slaves
? ModbusVersionMappingUtil.mapMasterToDowngradedVersion(configurationJson.master as ModbusMasterConfig)
: { slaves: [] },
},
configVersion: this.gatewayVersionIn
} as GatewayConnector<ModbusLegacyBasicConfig>;

View File

@ -25,8 +25,8 @@ import {
} from '@home/components/widget/lib/gateway/gateway-widget.models';
@Directive()
export abstract class ModbusBasicConfigDirective<BasicConfig>
extends GatewayConnectorBasicConfigDirective<ModbusBasicConfig_v3_5_2, BasicConfig> {
export abstract class ModbusBasicConfigDirective<InputBasicConfig, OutputBasicConfig>
extends GatewayConnectorBasicConfigDirective<InputBasicConfig, OutputBasicConfig> {
enableSlaveControl: FormControl<boolean> = new FormControl(false);
@ -41,7 +41,7 @@ export abstract class ModbusBasicConfigDirective<BasicConfig>
});
}
override writeValue(basicConfig: BasicConfig & ModbusBasicConfig): void {
override writeValue(basicConfig: OutputBasicConfig & ModbusBasicConfig): void {
super.writeValue(basicConfig);
this.onEnableSlaveControl(basicConfig);
}

View File

@ -20,7 +20,7 @@
<ng-container [ngTemplateOutlet]="generalTabContent"></ng-container>
</mat-tab>
<mat-tab label="{{ 'gateway.master-connections' | translate }}">
<tb-modbus-master-table formControlName="master"></tb-modbus-master-table>
<tb-modbus-master-table [isLegacy]="isLegacy" formControlName="master"></tb-modbus-master-table>
</mat-tab>
<mat-tab label="{{ 'gateway.server-config' | translate }}">
<div class="tb-form-panel no-border no-padding padding-top">

View File

@ -56,7 +56,9 @@ import {
],
styleUrls: ['./modbus-basic-config.component.scss'],
})
export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective<ModbusBasicConfig_v3_5_2> {
export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective<ModbusBasicConfig_v3_5_2, ModbusBasicConfig_v3_5_2> {
isLegacy = false;
protected override mapConfigToFormValue({ master, slave }: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 {
return {

View File

@ -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<ModbusLegacyBasicConfig> {
export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective<ModbusBasicConfig, ModbusLegacyBasicConfig> {
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<LegacySlaveConfig>,
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<LegacySlaveConfig>,
slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave as ModbusSlave) : {} as ModbusLegacySlave,
};
}
}

View File

@ -165,6 +165,11 @@
</mat-form-field>
</div>
</div>
<tb-report-strategy
*ngIf="withReportStrategy"
formControlName="reportStrategy"
[isExpansionMode]="true"
/>
</div>
</ng-template>
</mat-expansion-panel>

View File

@ -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<UntypedFormGroup>;
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 }],
});
}

View File

@ -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, ModbusSlaveInfo, ModbusValues>(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<ModbusLegacySlaveDialogComponent | ModbusSlaveDialogComponent> {
if (this.isLegacy) {
return this.dialog.open<ModbusLegacySlaveDialogComponent, ModbusSlaveInfo<LegacySlaveConfig>, ModbusValues>
(ModbusLegacySlaveDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
value: value as LegacySlaveConfig,
hideNewFields: true,
buttonTitle
}
});
}
return this.dialog.open<ModbusSlaveDialogComponent, ModbusSlaveInfo, ModbusValues>(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();

View File

@ -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<ModbusLegacySlaveDialogComponent, LegacySlaveConfig> {
constructor(
protected fb: FormBuilder,
protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo,
public dialogRef: MatDialogRef<ModbusLegacySlaveDialogComponent, LegacySlaveConfig>,
) {
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));
}
}

View File

@ -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<Component, Config> extends DialogComponent<Component, Config> implements OnDestroy {
slaveConfigFormGroup: UntypedFormGroup;
showSecurityControl: FormControl<boolean>;
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<void>();
constructor(
protected fb: FormBuilder,
protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo,
public dialogRef: MatDialogRef<Component, Config>,
) {
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;
}

View File

@ -227,13 +227,16 @@
</mat-form-field>
</div>
</div>
<div class="tb-form-row" fxLayoutAlign="space-between center">
<div *ngIf="data.hideNewFields else reportStrategy" class="tb-form-row" fxLayoutAlign="space-between center">
<mat-slide-toggle class="mat-slide" formControlName="sendDataOnlyOnChange">
<mat-label>
{{ 'gateway.send-data-on-change' | translate }}
</mat-label>
</mat-slide-toggle>
</div>
<ng-template #reportStrategy>
<tb-report-strategy formControlName="reportStrategy" [isExpansionMode]="true"/>
</ng-template>
<div class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>
@ -345,7 +348,7 @@
</mat-expansion-panel>
</div>
<div class="tb-form-panel stroked">
<tb-modbus-values [singleMode]="true" formControlName="values"></tb-modbus-values>
<tb-modbus-values [singleMode]="true" [hideNewFields]="data.hideNewFields" formControlName="values"></tb-modbus-values>
</div>
</div>
</div>

View File

@ -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<ModbusSlaveDialogComponent, SlaveConfig> implements OnDestroy {
slaveConfigFormGroup: UntypedFormGroup;
showSecurityControl: FormControl<boolean>;
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<void>();
export class ModbusSlaveDialogComponent extends ModbusSlaveDialogAbstract<ModbusSlaveDialogComponent, SlaveConfig> {
constructor(
private fb: FormBuilder,
protected fb: FormBuilder,
protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo,
public dialogRef: MatDialogRef<ModbusSlaveDialogComponent, SlaveConfig>,
) {
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<ModbusSlaveDialo
slaveResult.port = serialPort;
}
this.dialogRef.close(slaveResult);
}
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)]],
sendDataOnlyOnChange: [false],
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: [{}],
});
}
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}
);
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));
}
}

View File

@ -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,

View File

@ -0,0 +1,57 @@
<!--
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.
-->
<div [formGroup]="reportStrategyFormGroup" class="tb-form-panel stroked">
<mat-expansion-panel *ngIf="isExpansionMode else defaultMode" class="tb-settings" [expanded]="showStrategyControl.value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle fxLayoutAlign="center" [formControl]="showStrategyControl" class="mat-slide" (click)="$event.stopPropagation()">
<mat-label>
{{ 'gateway.report-strategy.label' | translate }}
</mat-label>
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-container [ngTemplateOutlet]="strategyFields"></ng-container>
</mat-expansion-panel>
<ng-template #defaultMode>
<div class="tb-form-panel-title" translate>gateway.report-strategy.label</div>
<ng-container [ngTemplateOutlet]="strategyFields"></ng-container>
</ng-template>
<ng-template #strategyFields>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width">{{ 'gateway.type' | translate }}</div>
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of reportStrategyTypes" [value]="type">{{ ReportTypeTranslateMap.get(type) | translate }}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="reportStrategyFormGroup.get('type').value !== ReportStrategyType.OnChange" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width tb-required">
<span tbTruncateWithTooltip translate>
gateway.report-strategy.report-period
</span>
</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" min="0" name="value" formControlName="reportPeriod" placeholder="{{ 'gateway.set' | translate }}"/>
</mat-form-field>
</div>
</div>
</ng-template>
</div>

View File

@ -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<boolean>;
readonly reportStrategyTypes = Object.values(ReportStrategyType);
readonly ReportTypeTranslateMap = ReportStrategyTypeTranslationsMap;
readonly ReportStrategyType = ReportStrategyType;
private onChange: (value: ReportStrategyConfig) => void;
private onTouched: () => void;
private destroy$ = new Subject<void>();
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});
}
}
}

View File

@ -299,5 +299,6 @@
</mat-label>
</mat-slide-toggle>
</div>
<tb-report-strategy *ngIf="connectorForm.get('type').value === ConnectorType.MODBUS && connectorForm.get('configVersion').value === GatewayVersion.Current" formControlName="reportStrategy"/>
</section>
</ng-template>

View File

@ -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<GatewayAttributeData>): void {
if (this.initialConnector) {
const clientConnectorData = data.data.find(attr => attr.key === this.initialConnector.name);

View File

@ -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<LegacySlaveConfig>;
slave: ModbusLegacySlave;
}
@ -588,9 +589,10 @@ export interface MappingInfo {
buttonTitle: string;
}
export interface ModbusSlaveInfo {
value: SlaveConfig;
export interface ModbusSlaveInfo<Slave = SlaveConfig> {
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, string>(
[
[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<ModbusValueKey, strin
]
);
export interface ModbusMasterConfig {
slaves: SlaveConfig[];
export interface ModbusMasterConfig<Slave = SlaveConfig> {
slaves: Slave[];
}
export interface LegacySlaveConfig extends Omit<SlaveConfig, 'reportStrategy'> {
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];

View File

@ -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<LegacySlaveConfig>): 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<LegacySlaveConfig> {
return {
slaves: master.slaves.map((slave: SlaveConfig) => {
const { reportStrategy, ...restSlave } = slave;
return {
...restSlave,
sendDataOnlyOnChange: reportStrategy?.type !== ReportStrategyType.OnReportPeriod
};
})
};
}

View File

@ -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,

View File

@ -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",

View File

@ -18,7 +18,6 @@
"unitId": 1,
"deviceName": "Temp Sensor",
"deviceType": "default",
"sendDataOnlyOnChange": true,
"connectAttemptTimeMs": 5000,
"connectAttemptCount": 5,
"waitAfterFailedAttemptsMs": 300000,