Merge pull request #11718 from maxunbearable/task/4143-rpc-templates-update
RPC templates update for MQTT, MODBUS, OPC-UA
This commit is contained in:
commit
61a0c65d3e
@ -16,20 +16,9 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
<ng-container [formGroup]="rpcParametersFormGroup">
|
<ng-container [formGroup]="rpcParametersFormGroup">
|
||||||
<div fxFlex fxLayout="row">
|
<div class="tb-form-hint tb-primary-fill tb-flex no-padding-top hint-container">
|
||||||
<mat-form-field fxFlex="100">
|
{{ 'gateway.rpc.hint.modbus-response-reading' | translate }}<br>
|
||||||
<mat-label>{{ 'gateway.key' | translate }}</mat-label>
|
{{ 'gateway.rpc.hint.modbus-writing-functions' | translate }}
|
||||||
<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>
|
||||||
<div fxFlex fxLayout="row" fxLayoutGap="10px">
|
<div fxFlex fxLayout="row" fxLayoutGap="10px">
|
||||||
<mat-form-field fxFlex="50" class="mat-block">
|
<mat-form-field fxFlex="50" class="mat-block">
|
||||||
@ -45,21 +34,6 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</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">
|
<div fxFlex fxLayout="row" fxLayoutGap="10px">
|
||||||
<mat-form-field fxFlex="50">
|
<mat-form-field fxFlex="50">
|
||||||
<mat-label>{{ 'gateway.rpc.address' | translate }}</mat-label>
|
<mat-label>{{ 'gateway.rpc.address' | translate }}</mat-label>
|
||||||
@ -88,5 +62,20 @@
|
|||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="writeFunctionCodes.includes(rpcParametersFormGroup.get('functionCode').value)" fxFlex fxLayout="row">
|
||||||
|
<mat-form-field fxFlex="100">
|
||||||
|
<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>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
:host {
|
||||||
|
.hint-container {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -39,13 +39,14 @@ import {
|
|||||||
ModbusEditableDataTypes,
|
ModbusEditableDataTypes,
|
||||||
ModbusFunctionCodeTranslationsMap,
|
ModbusFunctionCodeTranslationsMap,
|
||||||
ModbusObjectCountByDataType,
|
ModbusObjectCountByDataType,
|
||||||
ModbusValue,
|
|
||||||
noLeadTrailSpacesRegex,
|
noLeadTrailSpacesRegex,
|
||||||
|
RPCTemplateConfigModbus,
|
||||||
} from '@home/components/widget/lib/gateway/gateway-widget.models';
|
} from '@home/components/widget/lib/gateway/gateway-widget.models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-modbus-rpc-parameters',
|
selector: 'tb-modbus-rpc-parameters',
|
||||||
templateUrl: './modbus-rpc-parameters.component.html',
|
templateUrl: './modbus-rpc-parameters.component.html',
|
||||||
|
styleUrls: ['./modbus-rpc-parameters.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
@ -80,14 +81,13 @@ export class ModbusRpcParametersComponent implements ControlValueAccessor, Valid
|
|||||||
private readonly readFunctionCodes = [1, 2, 3, 4];
|
private readonly readFunctionCodes = [1, 2, 3, 4];
|
||||||
private readonly bitsFunctionCodes = [...this.readFunctionCodes, ...this.writeFunctionCodes];
|
private readonly bitsFunctionCodes = [...this.readFunctionCodes, ...this.writeFunctionCodes];
|
||||||
|
|
||||||
private onChange: (value: ModbusValue) => void;
|
private onChange: (value: RPCTemplateConfigModbus) => void;
|
||||||
private onTouched: () => void;
|
private onTouched: () => void;
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(private fb: FormBuilder) {
|
constructor(private fb: FormBuilder) {
|
||||||
this.rpcParametersFormGroup = this.fb.group({
|
this.rpcParametersFormGroup = this.fb.group({
|
||||||
tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
|
||||||
type: [ModbusDataType.BYTES, [Validators.required]],
|
type: [ModbusDataType.BYTES, [Validators.required]],
|
||||||
functionCode: [this.defaultFunctionCodes[0], [Validators.required]],
|
functionCode: [this.defaultFunctionCodes[0], [Validators.required]],
|
||||||
value: [{value: '', disabled: true}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
value: [{value: '', disabled: true}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
@ -106,7 +106,7 @@ export class ModbusRpcParametersComponent implements ControlValueAccessor, Valid
|
|||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerOnChange(fn: (value: ModbusValue) => void): void {
|
registerOnChange(fn: (value: RPCTemplateConfigModbus) => void): void {
|
||||||
this.onChange = fn;
|
this.onChange = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ export class ModbusRpcParametersComponent implements ControlValueAccessor, Valid
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
writeValue(value: ModbusValue): void {
|
writeValue(value: RPCTemplateConfigModbus): void {
|
||||||
this.rpcParametersFormGroup.patchValue(value, {emitEvent: false});
|
this.rpcParametersFormGroup.patchValue(value, {emitEvent: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
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">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'gateway.rpc.method-name' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="methodFilter"
|
||||||
|
placeholder="echo"/>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'gateway.rpc.requestTopicExpression' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="requestTopicExpression"
|
||||||
|
placeholder="sensor/${deviceName}/request/${methodName}/${requestId}"/>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-slide-toggle class="margin" (click)="$event.stopPropagation()" formControlName="withResponse">
|
||||||
|
{{ 'gateway.rpc.withResponse' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
<mat-form-field *ngIf="rpcParametersFormGroup.get('withResponse')?.value">
|
||||||
|
<mat-label>{{ 'gateway.rpc.responseTopicExpression' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="responseTopicExpression"
|
||||||
|
placeholder="sensor/${deviceName}/response/${methodName}/${requestId}"/>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field *ngIf="rpcParametersFormGroup.get('withResponse')?.value">
|
||||||
|
<mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="responseTimeout" type="number"
|
||||||
|
placeholder="10000" min="10" step="1"/>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="valueExpression"
|
||||||
|
placeholder="${params}"/>
|
||||||
|
</mat-form-field>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.mat-mdc-slide-toggle.margin {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
///
|
||||||
|
/// 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, tap } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
integerRegex,
|
||||||
|
noLeadTrailSpacesRegex,
|
||||||
|
RPCTemplateConfigMQTT
|
||||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-mqtt-rpc-parameters',
|
||||||
|
templateUrl: './mqtt-rpc-parameters.component.html',
|
||||||
|
styleUrls: ['./mqtt-rpc-parameters.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => MqttRpcParametersComponent),
|
||||||
|
multi: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => MqttRpcParametersComponent),
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class MqttRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy {
|
||||||
|
|
||||||
|
rpcParametersFormGroup: UntypedFormGroup;
|
||||||
|
|
||||||
|
private onChange: (value: RPCTemplateConfigMQTT) => void = (_) => {};
|
||||||
|
private onTouched: () => void = () => {};
|
||||||
|
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) {
|
||||||
|
this.rpcParametersFormGroup = this.fb.group({
|
||||||
|
methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
|
requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
|
responseTopicExpression: [{ value: null, disabled: true }, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
|
responseTimeout: [{ value: null, disabled: true }, [Validators.min(10), Validators.pattern(integerRegex)]],
|
||||||
|
valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
|
withResponse: [false, []],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.observeValueChanges();
|
||||||
|
this.observeWithResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: (value: RPCTemplateConfigMQTT) => 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: RPCTemplateConfigMQTT): void {
|
||||||
|
this.rpcParametersFormGroup.patchValue(value, {emitEvent: false});
|
||||||
|
this.toggleResponseFields(value.withResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private observeValueChanges(): void {
|
||||||
|
this.rpcParametersFormGroup.valueChanges.pipe(
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
).subscribe((value) => {
|
||||||
|
this.onChange(value);
|
||||||
|
this.onTouched();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private observeWithResponse(): void {
|
||||||
|
this.rpcParametersFormGroup.get('withResponse').valueChanges.pipe(
|
||||||
|
tap((isActive: boolean) => this.toggleResponseFields(isActive)),
|
||||||
|
takeUntil(this.destroy$),
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleResponseFields(enabled: boolean): void {
|
||||||
|
const responseTopicControl = this.rpcParametersFormGroup.get('responseTopicExpression');
|
||||||
|
const responseTimeoutControl = this.rpcParametersFormGroup.get('responseTimeout');
|
||||||
|
if (enabled) {
|
||||||
|
responseTopicControl.enable();
|
||||||
|
responseTimeoutControl.enable();
|
||||||
|
} else {
|
||||||
|
responseTopicControl.disable();
|
||||||
|
responseTimeoutControl.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
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 class="tb-form-hint tb-primary-fill tb-flex no-padding-top hint-container">
|
||||||
|
{{ 'gateway.rpc.hint.opc-method' | translate }}
|
||||||
|
</div>
|
||||||
|
<mat-form-field class="tb-flex">
|
||||||
|
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="method" placeholder="multiply"/>
|
||||||
|
</mat-form-field>
|
||||||
|
<fieldset class="tb-form-panel stroked arguments-container" fxLayout="column" formArrayName="arguments">
|
||||||
|
<strong>
|
||||||
|
<span class="fields-label">{{ 'gateway.rpc.arguments' | translate }}</span>
|
||||||
|
</strong>
|
||||||
|
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"
|
||||||
|
*ngFor="let argumentFormGroup of rpcParametersFormGroup.get('arguments')['controls']; let i = index" [formGroup]="argumentFormGroup">
|
||||||
|
<div class="tb-form-row column-xs type-container" fxLayoutAlign="space-between center">
|
||||||
|
<div class="tb-required" translate>gateway.type</div>
|
||||||
|
<div class="tb-flex no-gap">
|
||||||
|
<mat-form-field class="tb-flex no-gap fill-width" appearance="outline" subscriptSizing="dynamic">
|
||||||
|
<mat-select formControlName="type">
|
||||||
|
<mat-select-trigger>
|
||||||
|
<div class="tb-flex align-center">
|
||||||
|
<mat-icon class="tb-mat-18" [svgIcon]="valueTypes.get(argumentFormGroup.get('type').value)?.icon">
|
||||||
|
</mat-icon>
|
||||||
|
<span>{{ valueTypes.get(argumentFormGroup.get('type').value)?.name | translate }}</span>
|
||||||
|
</div>
|
||||||
|
</mat-select-trigger>
|
||||||
|
<mat-option *ngFor="let valueType of valueTypeKeys" [value]="valueType">
|
||||||
|
<mat-icon class="tb-mat-20" svgIcon="{{ valueTypes.get(valueType).icon }}">
|
||||||
|
</mat-icon>
|
||||||
|
<span>{{ valueTypes.get(valueType).name | translate }}</span>
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tb-form-row column-xs value-container" fxLayoutAlign="space-between center">
|
||||||
|
<div class="tb-required" translate>gateway.value</div>
|
||||||
|
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic" class="tb-inline-field flex tb-suffix-absolute">
|
||||||
|
<ng-container [ngSwitch]="argumentFormGroup.get('type').value">
|
||||||
|
<input *ngSwitchCase="MappingValueType.STRING" matInput required formControlName="string"
|
||||||
|
placeholder="{{ 'gateway.set' | translate }}" />
|
||||||
|
<input *ngSwitchCase="MappingValueType.INTEGER" matInput required formControlName="integer" type="number"
|
||||||
|
placeholder="{{ 'gateway.set' | translate }}" />
|
||||||
|
<input *ngSwitchCase="MappingValueType.DOUBLE" matInput required formControlName="double" type="number"
|
||||||
|
placeholder="{{ 'gateway.set' | translate }}" />
|
||||||
|
<mat-select *ngSwitchCase="MappingValueType.BOOLEAN" formControlName="boolean">
|
||||||
|
<mat-option [value]="true">true</mat-option>
|
||||||
|
<mat-option [value]="false">false</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</ng-container>
|
||||||
|
<mat-icon matSuffix
|
||||||
|
matTooltipPosition="above"
|
||||||
|
matTooltipClass="tb-error-tooltip"
|
||||||
|
[matTooltip]="('gateway.value-required') | translate"
|
||||||
|
*ngIf="argumentFormGroup.get(argumentFormGroup.get('type').value).hasError('required')
|
||||||
|
&& argumentFormGroup.get(argumentFormGroup.get('type').value).touched"
|
||||||
|
class="tb-error">
|
||||||
|
warning
|
||||||
|
</mat-icon>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<button mat-icon-button (click)="removeArgument(i)"
|
||||||
|
class="tb-box-button"
|
||||||
|
matTooltip="{{ 'gateway.rpc.remove' | translate }}"
|
||||||
|
matTooltipPosition="above">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button mat-raised-button
|
||||||
|
fxFlexAlign="start"
|
||||||
|
(click)="addArgument()">
|
||||||
|
{{ 'gateway.rpc.add-argument' | translate }}
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
:host {
|
||||||
|
.arguments-container {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-container {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-container {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint-container {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,169 @@
|
|||||||
|
///
|
||||||
|
/// 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,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
forwardRef,
|
||||||
|
OnDestroy,
|
||||||
|
} from '@angular/core';
|
||||||
|
import {
|
||||||
|
ControlValueAccessor,
|
||||||
|
FormArray,
|
||||||
|
FormBuilder,
|
||||||
|
FormGroup,
|
||||||
|
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 {
|
||||||
|
integerRegex,
|
||||||
|
MappingValueType,
|
||||||
|
mappingValueTypesMap,
|
||||||
|
noLeadTrailSpacesRegex,
|
||||||
|
OPCTypeValue,
|
||||||
|
RPCTemplateConfigOPC
|
||||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models';
|
||||||
|
import { isDefinedAndNotNull, isEqual } from '@core/utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-opc-rpc-parameters',
|
||||||
|
templateUrl: './opc-rpc-parameters.component.html',
|
||||||
|
styleUrls: ['./opc-rpc-parameters.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => OpcRpcParametersComponent),
|
||||||
|
multi: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => OpcRpcParametersComponent),
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class OpcRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy {
|
||||||
|
|
||||||
|
rpcParametersFormGroup: UntypedFormGroup;
|
||||||
|
|
||||||
|
readonly valueTypeKeys: MappingValueType[] = Object.values(MappingValueType);
|
||||||
|
readonly MappingValueType = MappingValueType;
|
||||||
|
readonly valueTypes = mappingValueTypesMap;
|
||||||
|
|
||||||
|
private onChange: (value: RPCTemplateConfigOPC) => void = (_) => {} ;
|
||||||
|
private onTouched: () => void = () => {};
|
||||||
|
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {
|
||||||
|
this.rpcParametersFormGroup = this.fb.group({
|
||||||
|
method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
|
arguments: this.fb.array([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.observeValueChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: (value: RPCTemplateConfigOPC) => void): void {
|
||||||
|
this.onChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: () => void): void {
|
||||||
|
this.onTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(): ValidationErrors | null {
|
||||||
|
return this.rpcParametersFormGroup.valid ? null : {
|
||||||
|
rpcParametersFormGroup: { valid: false }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
writeValue(params: RPCTemplateConfigOPC): void {
|
||||||
|
this.clearArguments();
|
||||||
|
params.arguments?.map(({type, value}) => ({type, [type]: value }))
|
||||||
|
.forEach(argument => this.addArgument(argument as OPCTypeValue));
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
this.rpcParametersFormGroup.get('method').patchValue(params.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
private observeValueChanges(): void {
|
||||||
|
this.rpcParametersFormGroup.valueChanges.pipe(
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
).subscribe(params => {
|
||||||
|
const updatedArguments = params.arguments.map(({type, ...config}) => ({type, value: config[type]}));
|
||||||
|
this.onChange({method: params.method, arguments: updatedArguments});
|
||||||
|
this.onTouched();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeArgument(index: number): void {
|
||||||
|
(this.rpcParametersFormGroup.get('arguments') as FormArray).removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
addArgument(value: OPCTypeValue = {} as OPCTypeValue): void {
|
||||||
|
const fromGroup = this.fb.group({
|
||||||
|
type: [value.type ?? MappingValueType.STRING],
|
||||||
|
string: [
|
||||||
|
value.string ?? { value: '', disabled: !(isEqual(value, {}) || value.string)},
|
||||||
|
[Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]
|
||||||
|
],
|
||||||
|
integer: [
|
||||||
|
{value: value.integer ?? 0, disabled: !isDefinedAndNotNull(value.integer)},
|
||||||
|
[Validators.required, Validators.pattern(integerRegex)]
|
||||||
|
],
|
||||||
|
double: [{value: value.double ?? 0, disabled: !isDefinedAndNotNull(value.double)}, [Validators.required]],
|
||||||
|
boolean: [{value: value.boolean ?? false, disabled: !isDefinedAndNotNull(value.boolean)}, [Validators.required]],
|
||||||
|
});
|
||||||
|
this.observeTypeChange(fromGroup);
|
||||||
|
(this.rpcParametersFormGroup.get('arguments') as FormArray).push(fromGroup, {emitEvent: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearArguments(): void {
|
||||||
|
const formArray = this.rpcParametersFormGroup.get('arguments') as FormArray;
|
||||||
|
while (formArray.length !== 0) {
|
||||||
|
formArray.removeAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private observeTypeChange(dataKeyFormGroup: FormGroup): void {
|
||||||
|
dataKeyFormGroup.get('type').valueChanges
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(type => {
|
||||||
|
dataKeyFormGroup.disable({emitEvent: false});
|
||||||
|
dataKeyFormGroup.get('type').enable({emitEvent: false});
|
||||||
|
dataKeyFormGroup.get(type).enable({emitEvent: false});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -70,8 +70,8 @@
|
|||||||
matTooltipPosition="above"
|
matTooltipPosition="above"
|
||||||
matTooltipClass="tb-error-tooltip"
|
matTooltipClass="tb-error-tooltip"
|
||||||
[matTooltip]="('gateway.value-required') | translate"
|
[matTooltip]="('gateway.value-required') | translate"
|
||||||
*ngIf="keyControl.get(keyControl.get('type').value).hasError('required')
|
*ngIf="keyControl.get(keyControl.get('value').value).hasError('required')
|
||||||
&& keyControl.get(keyControl.get('type').value).touched"
|
&& keyControl.get(keyControl.get('value').value).touched"
|
||||||
class="tb-error">
|
class="tb-error">
|
||||||
warning
|
warning
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
|
|||||||
@ -104,7 +104,7 @@ export class TypeValuePanelComponent implements ControlValueAccessor, Validator,
|
|||||||
dataKeyFormGroup.disable({emitEvent: false});
|
dataKeyFormGroup.disable({emitEvent: false});
|
||||||
dataKeyFormGroup.get('type').enable({emitEvent: false});
|
dataKeyFormGroup.get('type').enable({emitEvent: false});
|
||||||
dataKeyFormGroup.get(type).enable({emitEvent: false});
|
dataKeyFormGroup.get(type).enable({emitEvent: false});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteKey($event: Event, index: number): void {
|
deleteKey($event: Event, index: number): void {
|
||||||
|
|||||||
@ -44,7 +44,11 @@
|
|||||||
<div class="template-key">
|
<div class="template-key">
|
||||||
{{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}}
|
{{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!isObject(config.value) else RPCObjectRow"
|
<div *ngIf="isArray(config.value)" tbTruncateWithTooltip class="array-value">
|
||||||
|
{{ config.value | getRpcTemplateArrayView }}
|
||||||
|
</div>
|
||||||
|
<ng-container *ngIf="isObject(config.value)" [ngTemplateOutlet]="RPCObjectRow"></ng-container>
|
||||||
|
<div *ngIf="!isObject(config.value) && !isArray(config.value)"
|
||||||
[ngClass]="{'boolean-true': config.value === true,
|
[ngClass]="{'boolean-true': config.value === true,
|
||||||
'boolean-false': config.value === false }">
|
'boolean-false': config.value === false }">
|
||||||
<ng-container *ngIf="config.key === 'method' else value" [ngTemplateOutlet]="SNMPMethod"></ng-container>
|
<ng-container *ngIf="config.key === 'method' else value" [ngTemplateOutlet]="SNMPMethod"></ng-container>
|
||||||
|
|||||||
@ -104,6 +104,10 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.array-value {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,8 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit {
|
|||||||
rpcTemplates: Array<RPCTemplate>;
|
rpcTemplates: Array<RPCTemplate>;
|
||||||
|
|
||||||
public readonly originalOrder = (): number => 0;
|
public readonly originalOrder = (): number => 0;
|
||||||
public readonly isObject = (value: any) => isLiteralObject(value);
|
public readonly isObject = (value: unknown) => isLiteralObject(value);
|
||||||
|
public readonly isArray = (value: unknown) => Array.isArray(value);
|
||||||
public readonly SNMPMethodsTranslations = SNMPMethodsTranslations;
|
public readonly SNMPMethodsTranslations = SNMPMethodsTranslations;
|
||||||
|
|
||||||
constructor(private attributeService: AttributeService) {
|
constructor(private attributeService: AttributeService) {
|
||||||
|
|||||||
@ -20,36 +20,6 @@
|
|||||||
class="mat-subtitle-1 title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}</div>
|
class="mat-subtitle-1 title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}</div>
|
||||||
<ng-template [ngIf]="connectorType">
|
<ng-template [ngIf]="connectorType">
|
||||||
<ng-container [ngSwitch]="connectorType">
|
<ng-container [ngSwitch]="connectorType">
|
||||||
<ng-template [ngSwitchCase]="ConnectorType.MQTT">
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'gateway.rpc.methodFilter' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="methodFilter"
|
|
||||||
placeholder="echo"/>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'gateway.rpc.requestTopicExpression' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="requestTopicExpression"
|
|
||||||
placeholder="sensor/${deviceName}/request/${methodName}/${requestId}"/>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-slide-toggle class="margin" (click)="$event.stopPropagation()" formControlName="withResponse">
|
|
||||||
{{ 'gateway.rpc.withResponse' | translate }}
|
|
||||||
</mat-slide-toggle>
|
|
||||||
<mat-form-field *ngIf="commandForm.get('withResponse')?.value">
|
|
||||||
<mat-label>{{ 'gateway.rpc.responseTopicExpression' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="responseTopicExpression"
|
|
||||||
placeholder="sensor/${deviceName}/response/${methodName}/${requestId}"/>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field *ngIf="commandForm.get('withResponse')?.value">
|
|
||||||
<mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="responseTimeout" type="number"
|
|
||||||
placeholder="10000" min="10" step="1"/>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="valueExpression"
|
|
||||||
placeholder="${params}"/>
|
|
||||||
</mat-form-field>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngSwitchCase]="ConnectorType.BACNET">
|
<ng-template [ngSwitchCase]="ConnectorType.BACNET">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
|
<mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
|
||||||
@ -407,31 +377,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngSwitchCase]="ConnectorType.OPCUA" #OPCUAForm>
|
|
||||||
<mat-form-field >
|
|
||||||
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label>
|
|
||||||
<input matInput formControlName="method" placeholder="multiply"/>
|
|
||||||
</mat-form-field>
|
|
||||||
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="arguments">
|
|
||||||
<span class="fields-label">{{ 'gateway.rpc.arguments' | translate }}</span>
|
|
||||||
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"
|
|
||||||
*ngFor="let control of getFormArrayControls('arguments'); let i = index">
|
|
||||||
<mat-form-field appearance="outline" fxFlex>
|
|
||||||
<input matInput [formControl]="control" required/>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-icon style="cursor:pointer;"
|
|
||||||
fxFlex="30px"
|
|
||||||
(click)="removeOCPUAArguments(i)"
|
|
||||||
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete
|
|
||||||
</mat-icon>
|
|
||||||
</div>
|
|
||||||
<button mat-raised-button
|
|
||||||
fxFlexAlign="start"
|
|
||||||
(click)="addOCPUAArguments()">
|
|
||||||
{{ 'gateway.rpc.add-argument' | translate }}
|
|
||||||
</button>
|
|
||||||
</fieldset>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template ngSwitchDefault>
|
<ng-template ngSwitchDefault>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>{{ 'gateway.statistics.command' | translate }}</mat-label>
|
<mat-label>{{ 'gateway.statistics.command' | translate }}</mat-label>
|
||||||
|
|||||||
@ -51,7 +51,6 @@ import {
|
|||||||
} from '@shared/components/dialog/json-object-edit-dialog.component';
|
} from '@shared/components/dialog/json-object-edit-dialog.component';
|
||||||
import { jsonRequired } from '@shared/components/json-object-edit.component';
|
import { jsonRequired } from '@shared/components/json-object-edit.component';
|
||||||
import { deepClone } from '@core/utils';
|
import { deepClone } from '@core/utils';
|
||||||
import { takeUntil, tap } from "rxjs/operators";
|
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -129,7 +128,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
|
|||||||
this.propagateChange({...this.commandForm.value, ...value});
|
this.propagateChange({...this.commandForm.value, ...value});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.observeMQTTWithResponse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@ -141,16 +139,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
|
|||||||
let formGroup: FormGroup;
|
let formGroup: FormGroup;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ConnectorType.MQTT:
|
|
||||||
formGroup = this.fb.group({
|
|
||||||
methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
|
||||||
requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
|
||||||
responseTopicExpression: [{ value: null, disabled: true }, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
|
||||||
responseTimeout: [{ value: null, disabled: true }, [Validators.min(10), Validators.pattern(this.numbersOnlyPattern)]],
|
|
||||||
valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
|
||||||
withResponse: [false, []],
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case ConnectorType.BACNET:
|
case ConnectorType.BACNET:
|
||||||
formGroup = this.fb.group({
|
formGroup = this.fb.group({
|
||||||
method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
@ -246,12 +234,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
|
|||||||
httpHeaders: this.fb.array([]),
|
httpHeaders: this.fb.array([]),
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
case ConnectorType.OPCUA:
|
|
||||||
formGroup = this.fb.group({
|
|
||||||
method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
|
||||||
arguments: this.fb.array([]),
|
|
||||||
})
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
formGroup = this.fb.group({
|
formGroup = this.fb.group({
|
||||||
command: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
command: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
@ -293,18 +275,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
|
|||||||
return (this.commandForm.get(path) as FormArray).controls as FormControl[];
|
return (this.commandForm.get(path) as FormArray).controls as FormControl[];
|
||||||
}
|
}
|
||||||
|
|
||||||
addOCPUAArguments(value: string = null) {
|
|
||||||
const oidsFA = this.commandForm.get('arguments') as FormArray;
|
|
||||||
if (oidsFA) {
|
|
||||||
oidsFA.push(this.fb.control(value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]), {emitEvent: false});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeOCPUAArguments(index: number) {
|
|
||||||
const oidsFA = this.commandForm.get('arguments') as FormArray;
|
|
||||||
oidsFA.removeAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
openEditJSONDialog($event: Event) {
|
openEditJSONDialog($event: Event) {
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
@ -368,34 +338,8 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
|
|||||||
})
|
})
|
||||||
delete value.httpHeaders;
|
delete value.httpHeaders;
|
||||||
break;
|
break;
|
||||||
case ConnectorType.OPCUA:
|
|
||||||
this.clearFromArrayByName("arguments");
|
|
||||||
value.arguments.forEach(value => {
|
|
||||||
this.addOCPUAArguments(value)
|
|
||||||
})
|
|
||||||
delete value.arguments;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
this.commandForm.patchValue(value, {onlySelf: false});
|
this.commandForm.patchValue(value, {onlySelf: false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private observeMQTTWithResponse(): void {
|
|
||||||
if (this.connectorType === ConnectorType.MQTT) {
|
|
||||||
this.commandForm.get('withResponse').valueChanges.pipe(
|
|
||||||
tap((isActive: boolean) => {
|
|
||||||
const responseTopicControl = this.commandForm.get('responseTopicExpression');
|
|
||||||
const responseTimeoutControl = this.commandForm.get('responseTimeout');
|
|
||||||
if (isActive) {
|
|
||||||
responseTopicControl.enable();
|
|
||||||
responseTimeoutControl.enable();
|
|
||||||
} else {
|
|
||||||
responseTopicControl.disable();
|
|
||||||
responseTimeoutControl.disable();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
takeUntil(this.destroy$),
|
|
||||||
).subscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,16 +42,20 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #connectorForm>
|
<ng-template #connectorForm>
|
||||||
<tb-gateway-service-rpc-connector
|
<tb-gateway-service-rpc-connector
|
||||||
*ngIf="connectorType !== ConnectorType.MODBUS else modbusParameters"
|
*ngIf="!typesWithUpdatedParams.has(connectorType) else updatedParameters"
|
||||||
formControlName="params"
|
formControlName="params"
|
||||||
[connectorType]="connectorType"
|
[connectorType]="connectorType"
|
||||||
(sendCommand)="sendCommand()"
|
(sendCommand)="sendCommand()"
|
||||||
(saveTemplate)="saveTemplate()"
|
(saveTemplate)="saveTemplate()"
|
||||||
/>
|
/>
|
||||||
<ng-template #modbusParameters>
|
<ng-template #updatedParameters>
|
||||||
<div fxLayout="column" class="rpc-parameters">
|
<div fxLayout="column" class="rpc-parameters">
|
||||||
<div class="mat-subtitle-1 tb-form-panel-title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}</div>
|
<div class="mat-subtitle-1 tb-form-panel-title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}</div>
|
||||||
<tb-modbus-rpc-parameters formControlName="params"/>
|
<ng-container [ngSwitch]="connectorType">
|
||||||
|
<tb-modbus-rpc-parameters *ngSwitchCase="ConnectorType.MODBUS" formControlName="params"/>
|
||||||
|
<tb-mqtt-rpc-parameters *ngSwitchCase="ConnectorType.MQTT" formControlName="params"/>
|
||||||
|
<tb-opc-rpc-parameters *ngSwitchCase="ConnectorType.OPCUA" formControlName="params"/>
|
||||||
|
</ng-container>
|
||||||
<div class="template-actions" fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="10px">
|
<div class="template-actions" fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="10px">
|
||||||
<button mat-raised-button
|
<button mat-raised-button
|
||||||
(click)="saveTemplate()"
|
(click)="saveTemplate()"
|
||||||
|
|||||||
@ -74,6 +74,11 @@ export class GatewayServiceRPCComponent implements OnInit {
|
|||||||
|
|
||||||
readonly ConnectorType = ConnectorType;
|
readonly ConnectorType = ConnectorType;
|
||||||
readonly gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslatesMap;
|
readonly gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslatesMap;
|
||||||
|
readonly typesWithUpdatedParams = new Set<ConnectorType>([
|
||||||
|
ConnectorType.MQTT,
|
||||||
|
ConnectorType.OPCUA,
|
||||||
|
ConnectorType.MODBUS,
|
||||||
|
]);
|
||||||
|
|
||||||
private subscription: IWidgetSubscription;
|
private subscription: IWidgetSubscription;
|
||||||
private subscriptionOptions: WidgetSubscriptionOptions = {
|
private subscriptionOptions: WidgetSubscriptionOptions = {
|
||||||
|
|||||||
@ -297,7 +297,7 @@ export interface LegacyTimeseries {
|
|||||||
|
|
||||||
export interface RpcArgument {
|
export interface RpcArgument {
|
||||||
type: string;
|
type: string;
|
||||||
value: number;
|
value: number | string | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RpcMethod {
|
export interface RpcMethod {
|
||||||
@ -542,6 +542,37 @@ export interface RPCTemplateConfig {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RPCTemplateConfigMQTT {
|
||||||
|
methodFilter: string;
|
||||||
|
requestTopicExpression: string;
|
||||||
|
responseTopicExpression?: string;
|
||||||
|
responseTimeout?: number;
|
||||||
|
valueExpression: string;
|
||||||
|
withResponse: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RPCTemplateConfigModbus {
|
||||||
|
tag: string;
|
||||||
|
type: ModbusDataType;
|
||||||
|
functionCode?: number;
|
||||||
|
objectsCount: number;
|
||||||
|
address: number;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RPCTemplateConfigOPC {
|
||||||
|
method: string;
|
||||||
|
arguments: RpcArgument[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OPCTypeValue {
|
||||||
|
type: MappingValueType;
|
||||||
|
boolean?: boolean;
|
||||||
|
double?: number;
|
||||||
|
integer?: number;
|
||||||
|
string?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SaveRPCTemplateData {
|
export interface SaveRPCTemplateData {
|
||||||
config: RPCTemplateConfig;
|
config: RPCTemplateConfig;
|
||||||
templates: Array<RPCTemplate>;
|
templates: Array<RPCTemplate>;
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
///
|
||||||
|
/// 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 { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'getRpcTemplateArrayView',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class RpcTemplateArrayViewPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(values: {value: string | boolean | number}[]): string {
|
||||||
|
return values.map(({value}) => value.toString()).join(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -141,9 +141,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
TypeValuePanelComponent
|
TypeValuePanelComponent
|
||||||
} from '@home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component';
|
} from '@home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component';
|
||||||
import {
|
|
||||||
ModbusRpcParametersComponent
|
|
||||||
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component';
|
|
||||||
import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component';
|
import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component';
|
||||||
import {
|
import {
|
||||||
MqttLegacyBasicConfigComponent
|
MqttLegacyBasicConfigComponent
|
||||||
@ -161,7 +158,17 @@ import {
|
|||||||
ModbusLegacyBasicConfigComponent
|
ModbusLegacyBasicConfigComponent
|
||||||
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component';
|
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component';
|
||||||
import {
|
import {
|
||||||
ReportStrategyComponent
|
MqttRpcParametersComponent
|
||||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component';
|
||||||
|
import {
|
||||||
|
OpcRpcParametersComponent
|
||||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component';
|
||||||
|
import {
|
||||||
|
ModbusRpcParametersComponent
|
||||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component';
|
||||||
|
import { RpcTemplateArrayViewPipe } from '@home/components/widget/lib/gateway/pipes/rpc-template-array-view.pipe';
|
||||||
|
import {
|
||||||
|
ReportStrategyComponent
|
||||||
} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component';
|
} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -256,6 +263,10 @@ import {
|
|||||||
GatewayAdvancedConfigurationComponent,
|
GatewayAdvancedConfigurationComponent,
|
||||||
OpcUaLegacyBasicConfigComponent,
|
OpcUaLegacyBasicConfigComponent,
|
||||||
ModbusLegacyBasicConfigComponent,
|
ModbusLegacyBasicConfigComponent,
|
||||||
|
MqttRpcParametersComponent,
|
||||||
|
OpcRpcParametersComponent,
|
||||||
|
ModbusRpcParametersComponent,
|
||||||
|
RpcTemplateArrayViewPipe,
|
||||||
ReportStrategyComponent,
|
ReportStrategyComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
|||||||
@ -3142,10 +3142,11 @@
|
|||||||
"title": "{{type}} Connector RPC parameters",
|
"title": "{{type}} Connector RPC parameters",
|
||||||
"templates-title": "Connector RPC Templates",
|
"templates-title": "Connector RPC Templates",
|
||||||
"methodFilter": "Method filter",
|
"methodFilter": "Method filter",
|
||||||
|
"method-name": "Method name",
|
||||||
"requestTopicExpression": "Request topic expression",
|
"requestTopicExpression": "Request topic expression",
|
||||||
"responseTopicExpression": "Response topic expression",
|
"responseTopicExpression": "Response topic expression",
|
||||||
"responseTimeout": "Response Time",
|
"responseTimeout": "Response timeout",
|
||||||
"valueExpression": "Value Expression",
|
"valueExpression": "Value expression",
|
||||||
"tag": "Tag",
|
"tag": "Tag",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"functionCode": "Function Code",
|
"functionCode": "Function Code",
|
||||||
@ -3184,6 +3185,11 @@
|
|||||||
"tries": "Tries",
|
"tries": "Tries",
|
||||||
"httpHeaders": "HTTP Headers",
|
"httpHeaders": "HTTP Headers",
|
||||||
"header-name": "Header name",
|
"header-name": "Header name",
|
||||||
|
"hint": {
|
||||||
|
"modbus-response-reading": "RPC response will return all subtracted values from all connected devices when the reading functions are selected.",
|
||||||
|
"modbus-writing-functions": "RPC will write a filled value to all connected devices when the writing functions are selected.",
|
||||||
|
"opc-method": "A filled method name is the OPC-UA method that will processed on the server side (make sure your node has the requested method)."
|
||||||
|
},
|
||||||
"security-name": "Security name",
|
"security-name": "Security name",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"security": "Security",
|
"security": "Security",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user