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">
 | 
			
		||||
  <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 class="tb-form-hint tb-primary-fill tb-flex no-padding-top hint-container">
 | 
			
		||||
    {{ 'gateway.rpc.hint.modbus-response-reading' | translate }}<br>
 | 
			
		||||
    {{ 'gateway.rpc.hint.modbus-writing-functions' | translate }}
 | 
			
		||||
  </div>
 | 
			
		||||
  <div fxFlex fxLayout="row" fxLayoutGap="10px">
 | 
			
		||||
    <mat-form-field fxFlex="50" class="mat-block">
 | 
			
		||||
@ -45,21 +34,6 @@
 | 
			
		||||
      </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>
 | 
			
		||||
@ -88,5 +62,20 @@
 | 
			
		||||
      />
 | 
			
		||||
    </mat-form-field>
 | 
			
		||||
  </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>
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
  ModbusFunctionCodeTranslationsMap,
 | 
			
		||||
  ModbusObjectCountByDataType,
 | 
			
		||||
  ModbusValue,
 | 
			
		||||
  noLeadTrailSpacesRegex,
 | 
			
		||||
  RPCTemplateConfigModbus,
 | 
			
		||||
} from '@home/components/widget/lib/gateway/gateway-widget.models';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-modbus-rpc-parameters',
 | 
			
		||||
  templateUrl: './modbus-rpc-parameters.component.html',
 | 
			
		||||
  styleUrls: ['./modbus-rpc-parameters.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
@ -80,14 +81,13 @@ export class ModbusRpcParametersComponent implements ControlValueAccessor, Valid
 | 
			
		||||
  private readonly readFunctionCodes = [1, 2, 3, 4];
 | 
			
		||||
  private readonly bitsFunctionCodes = [...this.readFunctionCodes, ...this.writeFunctionCodes];
 | 
			
		||||
 | 
			
		||||
  private onChange: (value: ModbusValue) => void;
 | 
			
		||||
  private onChange: (value: RPCTemplateConfigModbus) => 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)]],
 | 
			
		||||
@ -106,7 +106,7 @@ export class ModbusRpcParametersComponent implements ControlValueAccessor, Valid
 | 
			
		||||
    this.destroy$.complete();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerOnChange(fn: (value: ModbusValue) => void): void {
 | 
			
		||||
  registerOnChange(fn: (value: RPCTemplateConfigModbus) => void): void {
 | 
			
		||||
    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});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -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"
 | 
			
		||||
                              matTooltipClass="tb-error-tooltip"
 | 
			
		||||
                              [matTooltip]="('gateway.value-required') | translate"
 | 
			
		||||
                              *ngIf="keyControl.get(keyControl.get('type').value).hasError('required')
 | 
			
		||||
                              && keyControl.get(keyControl.get('type').value).touched"
 | 
			
		||||
                              *ngIf="keyControl.get(keyControl.get('value').value).hasError('required')
 | 
			
		||||
                              && keyControl.get(keyControl.get('value').value).touched"
 | 
			
		||||
                              class="tb-error">
 | 
			
		||||
                      warning
 | 
			
		||||
                    </mat-icon>
 | 
			
		||||
 | 
			
		||||
@ -104,7 +104,7 @@ export class TypeValuePanelComponent implements ControlValueAccessor, Validator,
 | 
			
		||||
        dataKeyFormGroup.disable({emitEvent: false});
 | 
			
		||||
        dataKeyFormGroup.get('type').enable({emitEvent: false});
 | 
			
		||||
        dataKeyFormGroup.get(type).enable({emitEvent: false});
 | 
			
		||||
      })
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteKey($event: Event, index: number): void {
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,11 @@
 | 
			
		||||
      <div class="template-key">
 | 
			
		||||
        {{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}}
 | 
			
		||||
      </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,
 | 
			
		||||
                   'boolean-false': config.value === false  }">
 | 
			
		||||
        <ng-container *ngIf="config.key === 'method' else value" [ngTemplateOutlet]="SNMPMethod"></ng-container>
 | 
			
		||||
 | 
			
		||||
@ -104,6 +104,10 @@
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .array-value {
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -49,7 +49,8 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit {
 | 
			
		||||
  rpcTemplates: Array<RPCTemplate>;
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
  constructor(private attributeService: AttributeService) {
 | 
			
		||||
 | 
			
		||||
@ -20,36 +20,6 @@
 | 
			
		||||
    class="mat-subtitle-1 title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}</div>
 | 
			
		||||
  <ng-template [ngIf]="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">
 | 
			
		||||
        <mat-form-field>
 | 
			
		||||
          <mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
 | 
			
		||||
@ -407,31 +377,6 @@
 | 
			
		||||
          </button>
 | 
			
		||||
        </fieldset>
 | 
			
		||||
      </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>
 | 
			
		||||
        <mat-form-field>
 | 
			
		||||
          <mat-label>{{ 'gateway.statistics.command' | translate }}</mat-label>
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,6 @@ import {
 | 
			
		||||
} from '@shared/components/dialog/json-object-edit-dialog.component';
 | 
			
		||||
import { jsonRequired } from '@shared/components/json-object-edit.component';
 | 
			
		||||
import { deepClone } from '@core/utils';
 | 
			
		||||
import { takeUntil, tap } from "rxjs/operators";
 | 
			
		||||
import { Subject } from "rxjs";
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
@ -129,7 +128,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
 | 
			
		||||
        this.propagateChange({...this.commandForm.value, ...value});
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    this.observeMQTTWithResponse();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
@ -141,16 +139,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
 | 
			
		||||
    let formGroup: FormGroup;
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
        formGroup = this.fb.group({
 | 
			
		||||
          method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
 | 
			
		||||
@ -246,12 +234,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
 | 
			
		||||
          httpHeaders: this.fb.array([]),
 | 
			
		||||
        })
 | 
			
		||||
        break;
 | 
			
		||||
      case ConnectorType.OPCUA:
 | 
			
		||||
        formGroup = this.fb.group({
 | 
			
		||||
          method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
 | 
			
		||||
          arguments: this.fb.array([]),
 | 
			
		||||
        })
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        formGroup = this.fb.group({
 | 
			
		||||
          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[];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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) {
 | 
			
		||||
    if ($event) {
 | 
			
		||||
      $event.stopPropagation();
 | 
			
		||||
@ -368,34 +338,8 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C
 | 
			
		||||
          })
 | 
			
		||||
          delete value.httpHeaders;
 | 
			
		||||
          break;
 | 
			
		||||
        case ConnectorType.OPCUA:
 | 
			
		||||
          this.clearFromArrayByName("arguments");
 | 
			
		||||
          value.arguments.forEach(value => {
 | 
			
		||||
            this.addOCPUAArguments(value)
 | 
			
		||||
          })
 | 
			
		||||
          delete value.arguments;
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
      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-template #connectorForm>
 | 
			
		||||
      <tb-gateway-service-rpc-connector
 | 
			
		||||
        *ngIf="connectorType !== ConnectorType.MODBUS else modbusParameters"
 | 
			
		||||
        *ngIf="!typesWithUpdatedParams.has(connectorType) else updatedParameters"
 | 
			
		||||
        formControlName="params"
 | 
			
		||||
        [connectorType]="connectorType"
 | 
			
		||||
        (sendCommand)="sendCommand()"
 | 
			
		||||
        (saveTemplate)="saveTemplate()"
 | 
			
		||||
      />
 | 
			
		||||
      <ng-template #modbusParameters>
 | 
			
		||||
      <ng-template #updatedParameters>
 | 
			
		||||
        <div fxLayout="column" class="rpc-parameters">
 | 
			
		||||
          <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">
 | 
			
		||||
            <button mat-raised-button
 | 
			
		||||
                    (click)="saveTemplate()"
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,11 @@ export class GatewayServiceRPCComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  readonly ConnectorType = ConnectorType;
 | 
			
		||||
  readonly gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslatesMap;
 | 
			
		||||
  readonly typesWithUpdatedParams = new Set<ConnectorType>([
 | 
			
		||||
    ConnectorType.MQTT,
 | 
			
		||||
    ConnectorType.OPCUA,
 | 
			
		||||
    ConnectorType.MODBUS,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  private subscription: IWidgetSubscription;
 | 
			
		||||
  private subscriptionOptions: WidgetSubscriptionOptions = {
 | 
			
		||||
 | 
			
		||||
@ -297,7 +297,7 @@ export interface LegacyTimeseries {
 | 
			
		||||
 | 
			
		||||
export interface RpcArgument {
 | 
			
		||||
  type: string;
 | 
			
		||||
  value: number;
 | 
			
		||||
  value: number | string | boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RpcMethod {
 | 
			
		||||
@ -542,6 +542,37 @@ export interface RPCTemplateConfig {
 | 
			
		||||
  [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 {
 | 
			
		||||
  config: RPCTemplateConfig;
 | 
			
		||||
  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 {
 | 
			
		||||
  TypeValuePanelComponent
 | 
			
		||||
} 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 {
 | 
			
		||||
  MqttLegacyBasicConfigComponent
 | 
			
		||||
@ -161,7 +158,17 @@ import {
 | 
			
		||||
  ModbusLegacyBasicConfigComponent
 | 
			
		||||
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
@ -256,6 +263,10 @@ import {
 | 
			
		||||
    GatewayAdvancedConfigurationComponent,
 | 
			
		||||
    OpcUaLegacyBasicConfigComponent,
 | 
			
		||||
    ModbusLegacyBasicConfigComponent,
 | 
			
		||||
    MqttRpcParametersComponent,
 | 
			
		||||
    OpcRpcParametersComponent,
 | 
			
		||||
    ModbusRpcParametersComponent,
 | 
			
		||||
    RpcTemplateArrayViewPipe,
 | 
			
		||||
    ReportStrategyComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  exports: [
 | 
			
		||||
 | 
			
		||||
@ -3142,10 +3142,11 @@
 | 
			
		||||
            "title": "{{type}} Connector RPC parameters",
 | 
			
		||||
            "templates-title": "Connector RPC Templates",
 | 
			
		||||
            "methodFilter": "Method filter",
 | 
			
		||||
            "method-name": "Method name",
 | 
			
		||||
            "requestTopicExpression": "Request topic expression",
 | 
			
		||||
            "responseTopicExpression": "Response topic expression",
 | 
			
		||||
            "responseTimeout": "Response Time",
 | 
			
		||||
            "valueExpression": "Value Expression",
 | 
			
		||||
            "responseTimeout": "Response timeout",
 | 
			
		||||
            "valueExpression": "Value expression",
 | 
			
		||||
            "tag": "Tag",
 | 
			
		||||
            "type": "Type",
 | 
			
		||||
            "functionCode": "Function Code",
 | 
			
		||||
@ -3184,6 +3185,11 @@
 | 
			
		||||
            "tries": "Tries",
 | 
			
		||||
            "httpHeaders": "HTTP Headers",
 | 
			
		||||
            "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",
 | 
			
		||||
            "value": "Value",
 | 
			
		||||
            "security": "Security",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user