12 changed files with 322 additions and 125 deletions
@ -0,0 +1,92 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<ng-container [formGroup]="rpcParametersFormGroup"> |
|||
<div fxFlex fxLayout="row"> |
|||
<mat-form-field fxFlex="100"> |
|||
<mat-label>{{ 'gateway.key' | translate }}</mat-label> |
|||
<input matInput name="value" formControlName="tag" placeholder="{{ 'gateway.set' | translate }}"/> |
|||
<mat-icon matSuffix |
|||
matTooltipPosition="above" |
|||
matTooltipClass="tb-error-tooltip" |
|||
[matTooltip]="('gateway.key-required') | translate" |
|||
*ngIf="rpcParametersFormGroup.get('tag').hasError('required') && |
|||
rpcParametersFormGroup.get('tag').touched" |
|||
class="tb-error"> |
|||
warning |
|||
</mat-icon> |
|||
</mat-form-field> |
|||
</div> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="50" class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.type' | translate }}</mat-label> |
|||
<mat-select formControlName="type"> |
|||
<mat-option *ngFor="let type of modbusDataTypes" [value]="type">{{ type }}</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="50" class="mat-block"> |
|||
<mat-label>{{ 'gateway.rpc.functionCode' | translate }}</mat-label> |
|||
<mat-select formControlName="functionCode"> |
|||
<mat-option *ngFor="let code of functionCodes" [value]="code">{{ ModbusFunctionCodeTranslationsMap.get(code) | translate}}</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
</div> |
|||
<div fxFlex fxLayout="row"> |
|||
<mat-form-field fxFlex="100" *ngIf="writeFunctionCodes.includes(rpcParametersFormGroup.get('functionCode').value)"> |
|||
<mat-label>{{ 'gateway.rpc.value' | translate }}</mat-label> |
|||
<input matInput name="value" formControlName="value" placeholder="{{ 'gateway.set' | translate }}"/> |
|||
<mat-icon matSuffix |
|||
matTooltipPosition="above" |
|||
matTooltipClass="tb-error-tooltip" |
|||
[matTooltip]="('gateway.value-required') | translate" |
|||
*ngIf="rpcParametersFormGroup.get('value').hasError('required') && |
|||
rpcParametersFormGroup.get('value').touched" |
|||
class="tb-error"> |
|||
warning |
|||
</mat-icon> |
|||
</mat-form-field> |
|||
</div> |
|||
<div fxFlex fxLayout="row" fxLayoutGap="10px"> |
|||
<mat-form-field fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.address' | translate }}</mat-label> |
|||
<input matInput type="number" min="0" max="50000" name="value" formControlName="address" placeholder="{{ 'gateway.set' | translate }}"/> |
|||
<mat-icon matSuffix |
|||
matTooltipPosition="above" |
|||
matTooltipClass="tb-error-tooltip" |
|||
[matTooltip]="('gateway.address-required') | translate" |
|||
*ngIf="rpcParametersFormGroup.get('address').hasError('required') && |
|||
rpcParametersFormGroup.get('address').touched" |
|||
class="tb-error"> |
|||
warning |
|||
</mat-icon> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="50"> |
|||
<mat-label>{{ 'gateway.rpc.objectsCount' | translate }}</mat-label> |
|||
<input |
|||
matInput |
|||
type="number" |
|||
min="1" |
|||
max="50000" |
|||
name="value" |
|||
formControlName="objectsCount" |
|||
placeholder="{{ 'gateway.set' | translate }}" |
|||
[readonly]="!ModbusEditableDataTypes.includes(rpcParametersFormGroup.get('type').value)" |
|||
/> |
|||
</mat-form-field> |
|||
</div> |
|||
</ng-container> |
|||
|
|||
@ -0,0 +1,166 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
ChangeDetectionStrategy, |
|||
Component, |
|||
forwardRef, |
|||
OnDestroy, |
|||
} from '@angular/core'; |
|||
import { |
|||
ControlValueAccessor, |
|||
FormBuilder, |
|||
NG_VALIDATORS, |
|||
NG_VALUE_ACCESSOR, |
|||
UntypedFormGroup, |
|||
ValidationErrors, |
|||
Validator, |
|||
Validators |
|||
} from '@angular/forms'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { Subject } from 'rxjs'; |
|||
import { takeUntil } from 'rxjs/operators'; |
|||
import { |
|||
ModbusDataType, |
|||
ModbusEditableDataTypes, |
|||
ModbusFunctionCodeTranslationsMap, |
|||
ModbusObjectCountByDataType, |
|||
ModbusValue, |
|||
noLeadTrailSpacesRegex, |
|||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-modbus-rpc-parameters', |
|||
templateUrl: './modbus-rpc-parameters.component.html', |
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => ModbusRpcParametersComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => ModbusRpcParametersComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
standalone: true, |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
], |
|||
}) |
|||
export class ModbusRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy { |
|||
|
|||
rpcParametersFormGroup: UntypedFormGroup; |
|||
functionCodes: Array<number>; |
|||
|
|||
readonly ModbusEditableDataTypes = ModbusEditableDataTypes; |
|||
readonly ModbusFunctionCodeTranslationsMap = ModbusFunctionCodeTranslationsMap; |
|||
|
|||
readonly modbusDataTypes = Object.values(ModbusDataType) as ModbusDataType[]; |
|||
readonly writeFunctionCodes = [5, 6, 15, 16]; |
|||
|
|||
private readonly defaultFunctionCodes = [3, 4, 6, 16]; |
|||
private readonly readFunctionCodes = [1, 2, 3, 4]; |
|||
private readonly bitsFunctionCodes = [...this.readFunctionCodes, ...this.writeFunctionCodes]; |
|||
|
|||
private onChange: (value: ModbusValue) => void; |
|||
private onTouched: () => void; |
|||
|
|||
private destroy$ = new Subject<void>(); |
|||
|
|||
constructor(private fb: FormBuilder) { |
|||
this.rpcParametersFormGroup = this.fb.group({ |
|||
tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], |
|||
type: [ModbusDataType.BYTES, [Validators.required]], |
|||
functionCode: [this.defaultFunctionCodes[0], [Validators.required]], |
|||
value: [{value: '', disabled: true}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], |
|||
address: [null, [Validators.required]], |
|||
objectsCount: [1, [Validators.required]], |
|||
}); |
|||
|
|||
this.updateFunctionCodes(this.rpcParametersFormGroup.get('type').value); |
|||
this.observeValueChanges(); |
|||
this.observeKeyDataType(); |
|||
this.observeFunctionCode(); |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
this.destroy$.next(); |
|||
this.destroy$.complete(); |
|||
} |
|||
|
|||
registerOnChange(fn: (value: ModbusValue) => void): void { |
|||
this.onChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: () => void): void { |
|||
this.onTouched = fn; |
|||
} |
|||
|
|||
validate(): ValidationErrors | null { |
|||
return this.rpcParametersFormGroup.valid ? null : { |
|||
rpcParametersFormGroup: { valid: false } |
|||
}; |
|||
} |
|||
|
|||
writeValue(value: ModbusValue): void { |
|||
this.rpcParametersFormGroup.patchValue(value, {emitEvent: false}); |
|||
} |
|||
|
|||
private observeValueChanges(): void { |
|||
this.rpcParametersFormGroup.valueChanges.pipe( |
|||
takeUntil(this.destroy$) |
|||
).subscribe((value) => { |
|||
this.onChange(value); |
|||
this.onTouched(); |
|||
}); |
|||
} |
|||
|
|||
private observeKeyDataType(): void { |
|||
this.rpcParametersFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(dataType => { |
|||
if (!this.ModbusEditableDataTypes.includes(dataType)) { |
|||
this.rpcParametersFormGroup.get('objectsCount').patchValue(ModbusObjectCountByDataType[dataType], {emitEvent: false}); |
|||
} |
|||
this.updateFunctionCodes(dataType); |
|||
}); |
|||
} |
|||
|
|||
private observeFunctionCode(): void { |
|||
this.rpcParametersFormGroup.get('functionCode').valueChanges |
|||
.pipe(takeUntil(this.destroy$)) |
|||
.subscribe(code => this.updateValueEnabling(code)); |
|||
} |
|||
|
|||
private updateValueEnabling(code: number): void { |
|||
if (this.writeFunctionCodes.includes(code)) { |
|||
this.rpcParametersFormGroup.get('value').enable({emitEvent: false}); |
|||
} else { |
|||
this.rpcParametersFormGroup.get('value').setValue(null); |
|||
this.rpcParametersFormGroup.get('value').disable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
private updateFunctionCodes(dataType: ModbusDataType): void { |
|||
this.functionCodes = dataType === ModbusDataType.BITS ? this.bitsFunctionCodes : this.defaultFunctionCodes; |
|||
if (!this.functionCodes.includes(this.rpcParametersFormGroup.get('functionCode').value)) { |
|||
this.rpcParametersFormGroup.get('functionCode').patchValue(this.functionCodes[0], {emitEvent: false}); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue