Added Modbus Report Strategy
This commit is contained in:
parent
fb54dbc858
commit
1b36e5770a
@ -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>;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,6 +165,11 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<tb-report-strategy
|
||||
*ngIf="withReportStrategy"
|
||||
formControlName="reportStrategy"
|
||||
[isExpansionMode]="true"
|
||||
/>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-expansion-panel>
|
||||
|
||||
@ -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 }],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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, {}));
|
||||
}
|
||||
|
||||
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;
|
||||
super(fb, store, router, data, dialogRef);
|
||||
}
|
||||
|
||||
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);
|
||||
if (!slaveResult.reportStrategy) {
|
||||
delete slaveResult.reportStrategy;
|
||||
}
|
||||
|
||||
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: [{}],
|
||||
});
|
||||
return slaveResult;
|
||||
}
|
||||
|
||||
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 override addFieldsToFormGroup(): void {
|
||||
this.slaveConfigFormGroup.addControl('reportStrategy', this.fb.control(null));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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,
|
||||
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
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
"unitId": 1,
|
||||
"deviceName": "Temp Sensor",
|
||||
"deviceType": "default",
|
||||
"sendDataOnlyOnChange": true,
|
||||
"connectAttemptTimeMs": 5000,
|
||||
"connectAttemptCount": 5,
|
||||
"waitAfterFailedAttemptsMs": 300000,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user