connectors templates

This commit is contained in:
Maksym Dudnik 2023-12-14 13:21:57 +02:00
parent bccb2e7e8c
commit 6ee6ccaa36
12 changed files with 584 additions and 179 deletions

View File

@ -0,0 +1,54 @@
<!--
Copyright © 2016-2023 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.
-->
<mat-toolbar color="primary">
<h2 translate>gateway.rpc.save-template</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="close()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content style="width: 600px" class="mat-content" fxLayout="column">
<mat-form-field class="mat-block tb-value-type" style="flex-grow: 0">
<mat-label translate>gateway.rpc.template-name</mat-label>
<input matInput [formControl]="templateNameCtrl" required/>
<mat-error
*ngIf="templateNameCtrl.hasError('required')">
{{ 'gateway.rpc.template-name-required' | translate }}
</mat-error>
</mat-form-field>
<div class="mat-mdc-form-field-error"
style="margin-top: -15px; padding-left: 10px; font-size: 14px;"
*ngIf="validateDuplicateName(templateNameCtrl)">
{{ 'gateway.rpc.template-name-duplicate' | translate }}
</div>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button
type="button"
(click)="close()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="button"
[disabled]="!templateNameCtrl.valid"
(click)="save()">
{{ 'action.save' | translate }}
</button>
</div>

View File

@ -0,0 +1,62 @@
///
/// Copyright © 2016-2023 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 { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { FormBuilder, FormControl, UntypedFormControl, Validators } from '@angular/forms';
import { RPCTemplate, SaveRPCTemplateData } from '@home/components/widget/lib/gateway/gateway-widget.models';
@Component({
selector: 'gateway-service-rpc-connector-template-dialog',
templateUrl: './gateway-service-rpc-connector-template-dialog.html'
})
export class GatewayServiceRPCConnectorTemplateDialogComponent extends DialogComponent<GatewayServiceRPCConnectorTemplateDialogComponent, boolean> {
config: {
[key: string]: any;
};
templates: Array<RPCTemplate>;
templateNameCtrl: FormControl;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: SaveRPCTemplateData,
public dialogRef: MatDialogRef<GatewayServiceRPCConnectorTemplateDialogComponent, boolean>,
public fb: FormBuilder) {
super(store, router, dialogRef);
this.config = this.data.config;
this.templates = this.data.templates;
this.templateNameCtrl = this.fb.control('', [Validators.required])
}
validateDuplicateName(c: UntypedFormControl) {
const name = c.value.trim();
return !!this.templates.find((template) => template.name === name);
};
close(): void {
this.dialogRef.close();
}
save(): void {
this.dialogRef.close(this.templateNameCtrl.value);
}
}

View File

@ -22,13 +22,10 @@
{{template.name}} {{template.name}}
</mat-panel-title> </mat-panel-title>
<mat-panel-description> <mat-panel-description>
<button mat-icon-button matTooltip="Delete" (click)="deleteTemplate($event)"> <button mat-icon-button matTooltip="Delete" (click)="deleteTemplate($event, template)">
<mat-icon class="material-icons">delete</mat-icon> <mat-icon class="material-icons">delete</mat-icon>
</button> </button>
<button mat-icon-button matTooltip="Copy" (click)="copyTemplate($event)"> <button mat-icon-button matTooltip="Use" (click)="applyTemplate($event, template)">
<mat-icon class="material-icons">content_copy</mat-icon>
</button>
<button mat-icon-button matTooltip="Use" (click)="useTemplate($event)">
<mat-icon class="material-icons">play_arrow</mat-icon> <mat-icon class="material-icons">play_arrow</mat-icon>
</button> </button>
</mat-panel-description> </mat-panel-description>
@ -37,13 +34,15 @@
<ng-container <ng-container
*ngFor="let config of template.config | keyvalue : originalOrder" *ngFor="let config of template.config | keyvalue : originalOrder"
[ngTemplateOutlet]="RPCTemplateRef" [ngTemplateOutlet]="RPCTemplateRef"
[ngTemplateOutletContext]="{ $implicit: config, padding: false }"> [ngTemplateOutletContext]="{ $implicit: config, innerValue: false }">
</ng-container> </ng-container>
<ng-template #RPCTemplateRef let-config let-padding='padding'> <ng-template #RPCTemplateRef let-config let-innerValue='innerValue'>
<div [fxLayout]="isObject(config.value) ? 'column': 'row'" <div [fxLayout]="isObject(config.value) ? 'column': 'row'"
[fxLayoutAlign]="!isObject(config.value) ? 'space-between center' : ''" [fxLayoutAlign]="!isObject(config.value) ? 'space-between center' : ''"
[ngStyle]="{'padding-left': padding ? '16px': '0'}"> [ngStyle]="{'padding-left': innerValue ? '16px': '0'}">
<div class="template-key">{{config.key}}</div> <div class="template-key">
{{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}}
</div>
<div *ngIf="!isObject(config.value) else RPCObjectRow" <div *ngIf="!isObject(config.value) else RPCObjectRow"
[ngClass]="{'boolean-true': config.value === true, [ngClass]="{'boolean-true': config.value === true,
'boolean-false': config.value === false }"> 'boolean-false': config.value === false }">
@ -52,7 +51,7 @@
<ng-container <ng-container
*ngFor="let subConfig of config.value | keyvalue : originalOrder" *ngFor="let subConfig of config.value | keyvalue : originalOrder"
[ngTemplateOutlet]="RPCTemplateRef" [ngTemplateOutlet]="RPCTemplateRef"
[ngTemplateOutletContext]="{ $implicit: subConfig, padding: true }"> [ngTemplateOutletContext]="{ $implicit: subConfig, innerValue: true }">
</ng-container> </ng-container>
</ng-template> </ng-template>
</div> </div>

View File

@ -50,6 +50,11 @@
background-color: rgba(25, 128, 56, 0.08); background-color: rgba(25, 128, 56, 0.08);
} }
mat-expansion-panel {
margin-top: 10px;
overflow: visible;
}
.mat-expansion-panel-header-description { .mat-expansion-panel-header-description {
flex-direction: row-reverse; flex-direction: row-reverse;
align-items: center; align-items: center;

View File

@ -15,9 +15,16 @@
/// ///
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ConnectorType, RPCTemplate } from '@home/components/widget/lib/gateway/gateway-widget.models'; import {
import { TranslateService } from '@ngx-translate/core'; ConnectorType,
RPCTemplate
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { KeyValue } from '@angular/common'; import { KeyValue } from '@angular/common';
import { EntityType } from '@shared/models/entity-type.models';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { AttributeService } from '@core/http/attribute.service';
import { WidgetContext } from '@home/models/widget-component.models';
import { MatDialog } from '@angular/material/dialog';
@Component({ @Component({
selector: 'tb-gateway-service-rpc-connector-templates', selector: 'tb-gateway-service-rpc-connector-templates',
@ -29,52 +36,52 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit {
@Input() @Input()
connectorType: ConnectorType; connectorType: ConnectorType;
@Input()
ctx: WidgetContext;
@Output() @Output()
saveTemplate: EventEmitter<any> = new EventEmitter(); saveTemplate: EventEmitter<any> = new EventEmitter();
rpcTemplates: Array<RPCTemplate> = []; @Output()
useTemplate: EventEmitter<any> = new EventEmitter();
constructor(private translate: TranslateService) { @Input()
this.rpcTemplates.push( rpcTemplates: Array<RPCTemplate>;
{
name: 'Test Template', constructor(private attributeService: AttributeService) {
config: {
fieldString: 'string',
fieldNumber: 666,
fieldBool: true,
fieldArray: [111, 222, 333, "String", "444"],
fieldObj: {
subKey1: 'dasd',
subKey2: 666,
}
}
}
)
} }
ngOnInit() { ngOnInit() {
} }
public useTemplate($event: Event): void { public applyTemplate($event: Event, template: RPCTemplate): void {
$event.stopPropagation(); $event.stopPropagation();
console.log("useTemplate") this.useTemplate.emit(template);
} }
public copyTemplate($event: Event): void { public deleteTemplate($event: Event, template: RPCTemplate): void {
$event.stopPropagation(); $event.stopPropagation();
console.log("copyTemplate") const index = this.rpcTemplates.findIndex(data => {
return data.name == template.name;
})
this.rpcTemplates.splice(index, 1);
const key = `${this.connectorType}_template`;
this.attributeService.saveEntityAttributes(
{
id: this.ctx.defaultSubscription.targetDeviceId,
entityType: EntityType.DEVICE
}
, AttributeScope.SERVER_SCOPE, [{
key,
value: this.rpcTemplates
}]).subscribe(() => {
})
} }
public deleteTemplate($event: Event): void { public originalOrder = (a, b): number => {
$event.stopPropagation();
console.log("deleteTemplate")
}
public originalOrder = (a: KeyValue<string, any>, b: KeyValue<string, any>): number => {
return 0; return 0;
} }
public isObject(value: any) { public isObject(value: any) {
return value !== null && typeof value === 'object' && !Array.isArray(value); return value !== null && typeof value === 'object' && !Array.isArray(value);
} }

View File

@ -16,7 +16,8 @@
--> -->
<div fxLayout="column" class="command-form" [formGroup]="commandForm"> <div fxLayout="column" class="command-form" [formGroup]="commandForm">
<div class="mat-subtitle-1 title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}</div> <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"> <ng-template [ngSwitchCase]="ConnectorType.MQTT">
@ -26,27 +27,26 @@
placeholder="echo"/> placeholder="echo"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.request-topic-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.requestTopicExpression' | translate }}</mat-label>
<input matInput formControlName="requestTopicExpression" <input matInput formControlName="requestTopicExpression"
placeholder="sensor/${deviceName}/request/${methodName}/${requestId}"/> placeholder="sensor/${deviceName}/request/${methodName}/${requestId}"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.response-topic-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.responseTopicExpression' | translate }}</mat-label>
<input matInput formControlName="responseTopicExpression" <input matInput formControlName="responseTopicExpression"
placeholder="sensor/${deviceName}/response/${methodName}/${requestId}"/> placeholder="sensor/${deviceName}/response/${methodName}/${requestId}"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.response-timeout' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label>
<input matInput formControlName="responseTimeout" type="number" <input matInput formControlName="responseTimeout" type="number"
placeholder="10000" min="10" step="1"/> placeholder="10000" min="10" step="1"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" <input matInput formControlName="valueExpression"
placeholder="${params}"/> placeholder="${params}"/>
</mat-form-field> </mat-form-field>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.MODBUS"> <ng-template [ngSwitchCase]="ConnectorType.MODBUS">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.tag' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.tag' | translate }}</mat-label>
@ -62,7 +62,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex="50" class="mat-block"> <mat-form-field fxFlex="50" class="mat-block">
<mat-label>{{ 'gateway.rpc.function-code' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.functionCode' | translate }}</mat-label>
<mat-select formControlName="functionCode"> <mat-select formControlName="functionCode">
<mat-option *ngFor="let code of codesArray" [value]="code"> <mat-option *ngFor="let code of codesArray" [value]="code">
{{ code }} {{ code }}
@ -77,20 +77,19 @@
placeholder="1" step="1"/> placeholder="1" step="1"/>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex="50"> <mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.objects-count' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.objectsCount' | translate }}</mat-label>
<input matInput formControlName="objectsCount" type="number" min="0" <input matInput formControlName="objectsCount" type="number" min="0"
placeholder="31" step="1"/> placeholder="31" step="1"/>
</mat-form-field> </mat-form-field>
</div> </div>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.BACNET"> <ng-template [ngSwitchCase]="ConnectorType.BACNET">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
<input matInput formControlName="method" placeholder="set_state"/> <input matInput formControlName="method" placeholder="set_state"/>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.request-type' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.requestType' | translate }}</mat-label>
<mat-select formControlName="requestType"> <mat-select formControlName="requestType">
<mat-option *ngFor="let type of bACnetRequestTypes" [value]="type"> <mat-option *ngFor="let type of bACnetRequestTypes" [value]="type">
{{bACnetRequestTypesTranslates.get(type) | translate}} {{bACnetRequestTypesTranslates.get(type) | translate}}
@ -98,13 +97,13 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.request-timeout' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.requestTimeout' | translate }}</mat-label>
<input matInput formControlName="requestTimeout" type="number" <input matInput formControlName="requestTimeout" type="number"
min="10" step="1" placeholder="1000"/> min="10" step="1" placeholder="1000"/>
</mat-form-field> </mat-form-field>
<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">
<mat-label>{{ 'gateway.rpc.object-type' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.objectType' | translate }}</mat-label>
<mat-select formControlName="objectType"> <mat-select formControlName="objectType">
<mat-option *ngFor="let type of bACnetObjectTypes" [value]="type"> <mat-option *ngFor="let type of bACnetObjectTypes" [value]="type">
{{bACnetObjectTypesTranslates.get(type) | translate}} {{bACnetObjectTypesTranslates.get(type) | translate}}
@ -118,22 +117,21 @@
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.property-id' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.propertyId' | translate }}</mat-label>
<input matInput formControlName="propertyId" placeholder="presentValue"/> <input matInput formControlName="propertyId" placeholder="presentValue"/>
</mat-form-field> </mat-form-field>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.BLE"> <ng-template [ngSwitchCase]="ConnectorType.BLE">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
<input matInput formControlName="methodRPC" placeholder="rpcMethod1"/> <input matInput formControlName="methodRPC" placeholder="rpcMethod1"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.characteristic-uuid' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.characteristicUUID' | translate }}</mat-label>
<input matInput formControlName="characteristicUUID" placeholder="00002A00-0000-1000-8000-00805F9B34FB"/> <input matInput formControlName="characteristicUUID" placeholder="00002A00-0000-1000-8000-00805F9B34FB"/>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.method-processing' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodProcessing' | translate }}</mat-label>
<mat-select formControlName="methodProcessing"> <mat-select formControlName="methodProcessing">
<mat-option *ngFor="let type of bLEMethods" [value]="type"> <mat-option *ngFor="let type of bLEMethods" [value]="type">
{{bLEMethodsTranslates.get(type) | translate}} {{bLEMethodsTranslates.get(type) | translate}}
@ -141,35 +139,34 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-slide-toggle class="mat-slide" formControlName="withResponse"> <mat-slide-toggle class="mat-slide" formControlName="withResponse">
{{ 'gateway.rpc.with-response' | translate }} {{ 'gateway.rpc.withResponse' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.CAN"> <ng-template [ngSwitchCase]="ConnectorType.CAN">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
<input matInput formControlName="method" placeholder="sendSameData"/> <input matInput formControlName="method" placeholder="sendSameData"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.node-id' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.nodeID' | translate }}</mat-label>
<input matInput formControlName="nodeID" type="number" placeholder="4" min="0" step="1"/> <input matInput formControlName="nodeID" type="number" placeholder="4" min="0" step="1"/>
</mat-form-field> </mat-form-field>
<mat-slide-toggle class="mat-slide margin" formControlName="isExtendedID"> <mat-slide-toggle class="mat-slide margin" formControlName="isExtendedID">
{{ 'gateway.rpc.is-extended-id' | translate }} {{ 'gateway.rpc.isExtendedID' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<mat-slide-toggle class="mat-slide margin" formControlName="isFD"> <mat-slide-toggle class="mat-slide margin" formControlName="isFD">
{{ 'gateway.rpc.is-fd' | translate }} {{ 'gateway.rpc.isFD' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<mat-slide-toggle class="mat-slide margin" formControlName="bitrateSwitch"> <mat-slide-toggle class="mat-slide margin" formControlName="bitrateSwitch">
{{ 'gateway.rpc.bitrate-switch' | translate }} {{ 'gateway.rpc.bitrateSwitch' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<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.data-length' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.dataLength' | translate }}</mat-label>
<input matInput formControlName="dataLength" type="number" placeholder="2" min="1" step="1"/> <input matInput formControlName="dataLength" type="number" placeholder="2" min="1" step="1"/>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block" fxFlex="50"> <mat-form-field class="mat-block" fxFlex="50">
<mat-label>{{ 'gateway.rpc.data-byte-order' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.dataByteorder' | translate }}</mat-label>
<mat-select formControlName="dataByteorder"> <mat-select formControlName="dataByteorder">
<mat-option *ngFor="let order of cANByteOrders" [value]="order"> <mat-option *ngFor="let order of cANByteOrders" [value]="order">
{{ order | translate }} {{ order | translate }}
@ -179,59 +176,55 @@
</div> </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.data-before' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.dataBefore' | translate }}</mat-label>
<input matInput formControlName="dataBefore" placeholder="00AA"/> <input matInput formControlName="dataBefore" placeholder="00AA"/>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex="50"> <mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.data-after' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.dataAfter' | translate }}</mat-label>
<input matInput formControlName="dataAfter" placeholder="0102"/> <input matInput formControlName="dataAfter" placeholder="0102"/>
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.data-in-hex' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.dataInHEX' | translate }}</mat-label>
<input matInput formControlName="dataInHEX" <input matInput formControlName="dataInHEX"
placeholder="aa bb cc dd ee ff aa bb aa bb cc d ee ff"/> placeholder="aa bb cc dd ee ff aa bb aa bb cc d ee ff"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.data-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.dataExpression' | translate }}</mat-label>
<input matInput formControlName="dataExpression" <input matInput formControlName="dataExpression"
placeholder="userSpeed if maxAllowedSpeed > userSpeed else maxAllowedSpeed"/> placeholder="userSpeed if maxAllowedSpeed > userSpeed else maxAllowedSpeed"/>
</mat-form-field> </mat-form-field>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.FTP"> <ng-template [ngSwitchCase]="ConnectorType.FTP">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-filter' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodFilter' | translate }}</mat-label>
<input matInput formControlName="methodFilter" placeholder="read"/> <input matInput formControlName="methodFilter" placeholder="read"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" placeholder="${params}"/> <input matInput formControlName="valueExpression" placeholder="${params}"/>
</mat-form-field> </mat-form-field>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.OCPP"> <ng-template [ngSwitchCase]="ConnectorType.OCPP">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
<input matInput formControlName="methodRPC" placeholder="rpc1"/> <input matInput formControlName="methodRPC" placeholder="rpc1"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" placeholder="${params}"/> <input matInput formControlName="valueExpression" placeholder="${params}"/>
</mat-form-field> </mat-form-field>
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse"> <mat-slide-toggle class="mat-slide margin" formControlName="withResponse">
{{ 'gateway.rpc.with-response' | translate }} {{ 'gateway.rpc.withResponse' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.SOCKET"> <ng-template [ngSwitchCase]="ConnectorType.SOCKET">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
<input matInput formControlName="methodRPC" placeholder="rpcMethod1"/> <input matInput formControlName="methodRPC" placeholder="rpcMethod1"/>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.method-processing' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodProcessing' | translate }}</mat-label>
<mat-select formControlName="methodProcessing"> <mat-select formControlName="methodProcessing">
<mat-option *ngFor="let method of socketMethodProcessings" [value]="method"> <mat-option *ngFor="let method of socketMethodProcessings" [value]="method">
{{ SocketMethodProcessingsTranslates.get(method) | translate }} {{ SocketMethodProcessingsTranslates.get(method) | translate }}
@ -247,27 +240,25 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse"> <mat-slide-toggle class="mat-slide margin" formControlName="withResponse">
{{ 'gateway.rpc.with-response' | translate }} {{ 'gateway.rpc.withResponse' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.XMPP"> <ng-template [ngSwitchCase]="ConnectorType.XMPP">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodRPC' | translate }}</mat-label>
<input matInput formControlName="methodRPC" placeholder="rpc1"/> <input matInput formControlName="methodRPC" placeholder="rpc1"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" placeholder="${params}"/> <input matInput formControlName="valueExpression" placeholder="${params}"/>
</mat-form-field> </mat-form-field>
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse"> <mat-slide-toggle class="mat-slide margin" formControlName="withResponse">
{{ 'gateway.rpc.with-response' | translate }} {{ 'gateway.rpc.withResponse' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.SNMP"> <ng-template [ngSwitchCase]="ConnectorType.SNMP">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.request-filter' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.requestFilter' | translate }}</mat-label>
<input matInput formControlName="requestFilter" placeholder="setData"/> <input matInput formControlName="requestFilter" placeholder="setData"/>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
@ -298,15 +289,14 @@
</button> </button>
</fieldset> </fieldset>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.REST"> <ng-template [ngSwitchCase]="ConnectorType.REST">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-filter' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodFilter' | translate }}</mat-label>
<input matInput formControlName="methodFilter" placeholder="post_attributes"/> <input matInput formControlName="methodFilter" placeholder="post_attributes"/>
</mat-form-field> </mat-form-field>
<div fxFlex fxLayout="row" fxLayoutGap="10px"> <div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field class="mat-block" fxFlex="33"> <mat-form-field class="mat-block" fxFlex="33">
<mat-label>{{ 'gateway.rpc.http-method' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.HTTPMethod' | translate }}</mat-label>
<mat-select formControlName="HTTPMethod"> <mat-select formControlName="HTTPMethod">
<mat-option *ngFor="let method of hTTPMethods" [value]="method"> <mat-option *ngFor="let method of hTTPMethods" [value]="method">
{{ method }} {{ method }}
@ -314,14 +304,14 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex> <mat-form-field fxFlex>
<mat-label>{{ 'gateway.rpc.request-url' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.requestUrlExpression' | translate }}</mat-label>
<input matInput formControlName="requestUrlExpression" <input matInput formControlName="requestUrlExpression"
placeholder="http://127.0.0.1:5000/my_devices"/> placeholder="http://127.0.0.1:5000/my_devices"/>
</mat-form-field> </mat-form-field>
</div> </div>
<div fxFlex fxLayout="row" fxLayoutGap="10px"> <div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="33"> <mat-form-field fxFlex="33">
<mat-label>{{ 'gateway.rpc.response-timeout' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label>
<input matInput formControlName="responseTimeout" type="number" <input matInput formControlName="responseTimeout" type="number"
step="1" min="10" placeholder="10"/> step="1" min="10" placeholder="10"/>
</mat-form-field> </mat-form-field>
@ -337,11 +327,11 @@
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" placeholder="${params}"/> <input matInput formControlName="valueExpression" placeholder="${params}"/>
</mat-form-field> </mat-form-field>
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="httpHeaders"> <fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="httpHeaders">
<span class="fields-label">{{ 'gateway.rpc.http-headers' | translate }}</span> <span class="fields-label">{{ 'gateway.rpc.httpHeaders' | translate }}</span>
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('httpHeaders').length"> <div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('httpHeaders').length">
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"> <div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center">
<span fxFlex class="title">{{ 'gateway.rpc.header-name' | translate }}</span> <span fxFlex class="title">{{ 'gateway.rpc.header-name' | translate }}</span>
@ -405,15 +395,14 @@
</button> </button>
</fieldset> </fieldset>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.REQUEST"> <ng-template [ngSwitchCase]="ConnectorType.REQUEST">
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.method-filter' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.methodFilter' | translate }}</mat-label>
<input matInput formControlName="methodFilter" placeholder="echo"/> <input matInput formControlName="methodFilter" placeholder="echo"/>
</mat-form-field> </mat-form-field>
<div fxFlex fxLayout="row" fxLayoutGap="10px"> <div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field class="mat-block" fxFlex="33"> <mat-form-field class="mat-block" fxFlex="33">
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.httpMethod' | translate }}</mat-label>
<mat-select formControlName="httpMethod"> <mat-select formControlName="httpMethod">
<mat-option *ngFor="let method of hTTPMethods" [value]="method"> <mat-option *ngFor="let method of hTTPMethods" [value]="method">
{{ method }} {{ method }}
@ -421,13 +410,13 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex> <mat-form-field fxFlex>
<mat-label>{{ 'gateway.rpc.request-url' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.requestUrlExpression' | translate }}</mat-label>
<input matInput formControlName="requestUrlExpression" placeholder="http://127.0.0.1:5000/my_devices"/> <input matInput formControlName="requestUrlExpression" placeholder="http://127.0.0.1:5000/my_devices"/>
</mat-form-field> </mat-form-field>
</div> </div>
<div fxFlex fxLayout="row" fxLayoutGap="10px"> <div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="33"> <mat-form-field fxFlex="33">
<mat-label>{{ 'gateway.rpc.response-timeout' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label>
<input matInput formControlName="responseTimeout" type="number" <input matInput formControlName="responseTimeout" type="number"
step="1" min="10" placeholder="10"/> step="1" min="10" placeholder="10"/>
</mat-form-field> </mat-form-field>
@ -443,15 +432,15 @@
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.request-value-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.requestValueExpression' | translate }}</mat-label>
<input matInput formControlName="requestValueExpression" placeholder="${params}"/> <input matInput formControlName="requestValueExpression" placeholder="${params}"/>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'gateway.rpc.response-value-expression' | translate }}</mat-label> <mat-label>{{ 'gateway.rpc.responseValueExpression' | translate }}</mat-label>
<input matInput formControlName="responseValueExpression" placeholder="${temp}"/> <input matInput formControlName="responseValueExpression" placeholder="${temp}"/>
</mat-form-field> </mat-form-field>
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="httpHeaders"> <fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="httpHeaders">
<span class="fields-label">{{ 'gateway.rpc.http-headers' | translate }}</span> <span class="fields-label">{{ 'gateway.rpc.httpHeaders' | translate }}</span>
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('httpHeaders').length"> <div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('httpHeaders').length">
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"> <div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center">
<span fxFlex class="title">{{ 'gateway.rpc.header-name' | translate }}</span> <span fxFlex class="title">{{ 'gateway.rpc.header-name' | translate }}</span>
@ -483,7 +472,6 @@
</button> </button>
</fieldset> </fieldset>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="ConnectorType.OPCUA_ASYNCIO"> <ng-template [ngSwitchCase]="ConnectorType.OPCUA_ASYNCIO">
<ng-container *ngTemplateOutlet="OPCUAForm"></ng-container> <ng-container *ngTemplateOutlet="OPCUAForm"></ng-container>
</ng-template> </ng-template>
@ -513,11 +501,27 @@
</button> </button>
</fieldset> </fieldset>
</ng-template> </ng-template>
<ng-template ngSwitchDefault>
{{'dsadas'}}
<mat-form-field>
<mat-label>{{ 'gateway.statistics.command' | translate }}</mat-label>
<input matInput formControlName="command"/>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'widget-config.datasource-parameters' | translate }}</mat-label>
<input matInput formControlName="params" type="JSON"/>
<mat-icon class="material-icons-outlined" aria-hidden="false" aria-label="help-icon"
matIconSuffix style="cursor:pointer;"
(click)="openEditJSONDialog($event)"
matTooltip="{{ 'gateway.rpc-command-edit-params' | translate }}">edit
</mat-icon>
</mat-form-field>
</ng-template>
</ng-container> </ng-container>
</ng-template> </ng-template>
<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)="sendCommand.emit()" (click)="save()"
[disabled]="commandForm.invalid"> [disabled]="commandForm.invalid">
{{ 'gateway.rpc-command-save-template' | translate }} {{ 'gateway.rpc-command-save-template' | translate }}
</button> </button>

View File

@ -14,27 +14,57 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { import {
BACnetObjectTypes, BACnetObjectTypesTranslates, ControlValueAccessor,
BACnetRequestTypes, BACnetRequestTypesTranslates, BLEMethods, BLEMethodsTranslates, FormArray,
FormBuilder,
FormControl,
FormGroup,
NG_VALUE_ACCESSOR,
Validators
} from '@angular/forms';
import {
BACnetObjectTypes,
BACnetObjectTypesTranslates,
BACnetRequestTypes,
BACnetRequestTypesTranslates,
BLEMethods,
BLEMethodsTranslates,
CANByteOrders, CANByteOrders,
ConnectorType, GatewayConnectorDefaultTypesTranslates, HTTPMethods, ConnectorType,
GatewayConnectorDefaultTypesTranslates,
HTTPMethods,
ModbusCommandTypes, ModbusCommandTypes,
RPCCommand, RPCCommand,
SNMPMethods, SNMPMethodsTranslations, RPCTemplateConfig,
SNMPMethods,
SNMPMethodsTranslations,
SocketEncodings, SocketEncodings,
SocketMethodProcessings, SocketMethodProcessingsTranslates SocketMethodProcessings,
SocketMethodProcessingsTranslates
} from '@home/components/widget/lib/gateway/gateway-widget.models'; } from '@home/components/widget/lib/gateway/gateway-widget.models';
import { TranslateService } from "@ngx-translate/core"; import { MatDialog } from '@angular/material/dialog';
import {
JsonObjectEditDialogComponent,
JsonObjectEditDialogData
} from '@shared/components/dialog/json-object-edit-dialog.component';
import { jsonRequired } from '@shared/components/json-object-edit.component';
import { deepClone } from '@core/utils';
@Component({ @Component({
selector: 'tb-gateway-service-rpc-connector', selector: 'tb-gateway-service-rpc-connector',
templateUrl: './gateway-service-rpc-connector.component.html', templateUrl: './gateway-service-rpc-connector.component.html',
styleUrls: ['./gateway-service-rpc-connector.component.scss'] styleUrls: ['./gateway-service-rpc-connector.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => GatewayServiceRPCConnectorComponent),
multi: true
}
]
}) })
export class GatewayServiceRPCConnectorComponent implements OnInit { export class GatewayServiceRPCConnectorComponent implements OnInit, ControlValueAccessor {
@Input() @Input()
connectorType: ConnectorType; connectorType: ConnectorType;
@ -42,6 +72,9 @@ export class GatewayServiceRPCConnectorComponent implements OnInit {
@Output() @Output()
sendCommand: EventEmitter<RPCCommand> = new EventEmitter(); sendCommand: EventEmitter<RPCCommand> = new EventEmitter();
@Output()
saveTemplate: EventEmitter<RPCTemplateConfig> = new EventEmitter();
commandForm: FormGroup; commandForm: FormGroup;
codesArray: Array<number> = [1, 2, 3, 4, 5, 6, 15, 16]; codesArray: Array<number> = [1, 2, 3, 4, 5, 6, 15, 16];
@ -74,14 +107,41 @@ export class GatewayServiceRPCConnectorComponent implements OnInit {
'(\\#[-a-z\\d_]*)?$', // fragment locator '(\\#[-a-z\\d_]*)?$', // fragment locator
'i' 'i'
); );
private propagateChange = (v: any) => {
}
constructor(private fb: FormBuilder, constructor(private fb: FormBuilder,
private translate: TranslateService) { private dialog: MatDialog,) {
} }
ngOnInit() { ngOnInit() {
this.commandForm = this.connectorParamsFormGroupByType(this.connectorType) this.commandForm = this.connectorParamsFormGroupByType(this.connectorType);
this.commandForm.valueChanges.subscribe(value => {
const httpHeaders = {};
const security = {};
switch (this.connectorType) {
case ConnectorType.REST:
value.httpHeaders.forEach(data => {
httpHeaders[data.headerName] = data.value;
})
value.httpHeaders = httpHeaders;
value.security.forEach(data => {
security[data.securityName] = data.value;
})
value.security = security;
break;
case ConnectorType.REQUEST:
value.httpHeaders.forEach(data => {
httpHeaders[data.headerName] = data.value;
})
value.httpHeaders = httpHeaders;
break;
}
if (this.commandForm.valid) {
this.propagateChange({...this.commandForm.value,...value});
}
})
} }
connectorParamsFormGroupByType(type: ConnectorType): FormGroup { connectorParamsFormGroupByType(type: ConnectorType): FormGroup {
@ -206,6 +266,13 @@ export class GatewayServiceRPCConnectorComponent implements OnInit {
method: [null, [Validators.required]], method: [null, [Validators.required]],
arguments: this.fb.array([]), arguments: this.fb.array([]),
}) })
break;
default:
formGroup = this.fb.group({
command: [null, [Validators.required]],
params: ['{}', [jsonRequired]],
})
} }
return formGroup; return formGroup;
} }
@ -213,7 +280,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit {
addSNMPoid(value: string = null) { addSNMPoid(value: string = null) {
const oidsFA = this.commandForm.get('oid') as FormArray; const oidsFA = this.commandForm.get('oid') as FormArray;
if (oidsFA) { if (oidsFA) {
oidsFA.push(this.fb.control(value, [Validators.required])); oidsFA.push(this.fb.control(value, [Validators.required]), {emitEvent: false});
} }
} }
@ -229,7 +296,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit {
value: [value.value, [Validators.required]] value: [value.value, [Validators.required]]
}) })
if (headerFA) { if (headerFA) {
headerFA.push(formGroup); headerFA.push(formGroup, {emitEvent: false});
} }
} }
@ -245,7 +312,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit {
value: [value.value, [Validators.required]] value: [value.value, [Validators.required]]
}) })
if (securityFA) { if (securityFA) {
securityFA.push(formGroup); securityFA.push(formGroup, {emitEvent: false});
} }
} }
@ -261,7 +328,7 @@ export class GatewayServiceRPCConnectorComponent implements OnInit {
addOCPUAArguments(value: string = null) { addOCPUAArguments(value: string = null) {
const oidsFA = this.commandForm.get('arguments') as FormArray; const oidsFA = this.commandForm.get('arguments') as FormArray;
if (oidsFA) { if (oidsFA) {
oidsFA.push(this.fb.control(value)); oidsFA.push(this.fb.control(value), {emitEvent: false});
} }
} }
@ -269,4 +336,86 @@ export class GatewayServiceRPCConnectorComponent implements OnInit {
const oidsFA = this.commandForm.get('arguments') as FormArray; const oidsFA = this.commandForm.get('arguments') as FormArray;
oidsFA.removeAt(index); oidsFA.removeAt(index);
} }
openEditJSONDialog($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<JsonObjectEditDialogComponent, JsonObjectEditDialogData, object>(JsonObjectEditDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
jsonValue: JSON.parse(this.commandForm.get('params').value),
required: true
}
}).afterClosed().subscribe(
(res) => {
if (res) {
this.commandForm.get('params').setValue(JSON.stringify(res));
}
}
);
}
save() {
this.saveTemplate.emit();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
clearFromArrayByName(name: string) {
const formArray = this.commandForm.get(name) as FormArray;
while (formArray.length !== 0) {
formArray.removeAt(0)
}
}
writeValue(value: RPCTemplateConfig): void {
if (typeof value == "object") {
value = deepClone(value);
switch (this.connectorType) {
case ConnectorType.SNMP:
this.clearFromArrayByName("oids");
value.oids.forEach(value => {
this.addSNMPoid(value)
})
delete value.oids;
break;
case ConnectorType.REQUEST:
this.clearFromArrayByName("httpHeaders");
value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => {
this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string})
})
delete value.httpHeaders;
break;
case ConnectorType.REST:
this.clearFromArrayByName("httpHeaders");
this.clearFromArrayByName("security");
value.security && Object.entries(value.security).forEach(securityHeader => {
this.addHTTPSecurity({securityName: securityHeader[0], value: securityHeader[1] as string})
})
delete value.security;
value.httpHeaders && Object.entries(value.httpHeaders).forEach(httpHeader => {
this.addHTTPHeader({headerName: httpHeader[0], value: httpHeader[1] as string})
})
delete value.httpHeaders;
break;
case ConnectorType.OPCUA:
case ConnectorType.OPCUA_ASYNCIO:
this.clearFromArrayByName("arguments");
value.arguments.forEach(value => {
this.addOCPUAArguments(value)
})
delete value.arguments;
break;
}
this.commandForm.patchValue(value, {onlySelf: false});
}
}
} }

View File

@ -41,7 +41,8 @@
</button> </button>
</ng-container> </ng-container>
<ng-template #connectorForm> <ng-template #connectorForm>
<tb-gateway-service-rpc-connector [connectorType]="connectorType" (sendCommand)="sendCommand()"/> <tb-gateway-service-rpc-connector formControlName="params" [connectorType]="connectorType"
(sendCommand)="sendCommand()" (saveTemplate)="saveTemplate()"/>
</ng-template> </ng-template>
</div> </div>
<section class="result-block" [formGroup]="commandForm"> <section class="result-block" [formGroup]="commandForm">
@ -52,4 +53,6 @@
<tb-json-content [contentType]="contentTypes.JSON" readonly="true" formControlName="result"></tb-json-content> <tb-json-content [contentType]="contentTypes.JSON" readonly="true" formControlName="result"></tb-json-content>
</section> </section>
</div> </div>
<tb-gateway-service-rpc-connector-templates fxFlex="30" *ngIf="isConnector" class="border"></tb-gateway-service-rpc-connector-templates> <tb-gateway-service-rpc-connector-templates fxFlex="30" *ngIf="isConnector" class="border" [rpcTemplates]="templates"
[ctx]="ctx" [connectorType]="connectorType" (useTemplate)="useTemplate($event)">
</tb-gateway-service-rpc-connector-templates>

View File

@ -14,17 +14,28 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { OnInit, Component, Input } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { ContentType } from '@shared/models/constants'; import { ContentType } from '@shared/models/constants';
import {
JsonObjectEditDialogComponent,
JsonObjectEditDialogData
} 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 { ConnectorType, RPCCommand } from '@home/components/widget/lib/gateway/gateway-widget.models'; import {
ConnectorType,
RPCCommand,
RPCTemplate,
RPCTemplateConfig,
SaveRPCTemplateData
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import {
GatewayServiceRPCConnectorTemplateDialogComponent
} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog';
import { AttributeService } from '@core/http/attribute.service';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { EntityType } from '@shared/models/entity-type.models';
import { DatasourceType, widgetType } from '@shared/models/widget.models';
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service';
@Component({ @Component({
selector: 'tb-gateway-service-rpc', selector: 'tb-gateway-service-rpc',
@ -58,9 +69,27 @@ export class GatewayServiceRPCComponent implements OnInit {
]; ];
public connectorType: ConnectorType; public connectorType: ConnectorType;
public templates: Array<RPCTemplate> = [];
private subscription: IWidgetSubscription;
private subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {
onDataUpdated: () => this.ctx.ngZone.run(() => {
this.updateTemplates()
}),
onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => {
this.onDataUpdateError(e);
}),
dataLoading: () => {
}
}
};
constructor(private fb: FormBuilder, constructor(private fb: FormBuilder,
private dialog: MatDialog) { private dialog: MatDialog,
private utils: UtilsService,
private cd: ChangeDetectorRef,
private attributeService: AttributeService) {
this.commandForm = this.fb.group({ this.commandForm = this.fb.group({
command: [null, [Validators.required]], command: [null, [Validators.required]],
time: [60, [Validators.required, Validators.min(1)]], time: [60, [Validators.required, Validators.min(1)]],
@ -75,6 +104,17 @@ export class GatewayServiceRPCComponent implements OnInit {
this.commandForm.get('command').setValue(this.RPCCommands[0]); this.commandForm.get('command').setValue(this.RPCCommands[0]);
} else { } else {
this.connectorType = this.ctx.stateController.getStateParams().connector_rpc.value.type; this.connectorType = this.ctx.stateController.getStateParams().connector_rpc.value.type;
const subscriptionInfo = [{
type: DatasourceType.entity,
entityType: EntityType.DEVICE,
entityId: this.ctx.defaultSubscription.targetDeviceId,
entityName: 'Connector',
attributes: [{name: `${this.connectorType}_template`}]
}];
this.ctx.subscriptionApi.createSubscriptionFromInfo(widgetType.latest, subscriptionInfo,
this.subscriptionOptions, false, true).subscribe(subscription => {
this.subscription = subscription;
})
} }
} }
@ -82,7 +122,9 @@ export class GatewayServiceRPCComponent implements OnInit {
this.resultTime = null; this.resultTime = null;
const formValues = value || this.commandForm.value; const formValues = value || this.commandForm.value;
const commandPrefix = this.isConnector ? `${this.connectorType}_` : 'gateway_'; const commandPrefix = this.isConnector ? `${this.connectorType}_` : 'gateway_';
this.ctx.controlApi.sendTwoWayCommand(commandPrefix + formValues.command.toLowerCase(), formValues.params, formValues.time).subscribe({ const command = !this.isConnector ? formValues.command.toLowerCase() : this.getCommandFromParamsByType(formValues.params);
const params = formValues.params;
this.ctx.controlApi.sendTwoWayCommand(commandPrefix + command, params, formValues.time).subscribe({
next: resp => { next: resp => {
this.resultTime = new Date().getTime(); this.resultTime = new Date().getTime();
this.commandForm.get('result').setValue(JSON.stringify(resp)) this.commandForm.get('result').setValue(JSON.stringify(resp))
@ -95,23 +137,86 @@ export class GatewayServiceRPCComponent implements OnInit {
}); });
} }
openEditJSONDialog($event: Event) { getCommandFromParamsByType(params: RPCTemplateConfig) {
if ($event) { switch (this.connectorType) {
$event.stopPropagation(); case ConnectorType.MQTT:
case ConnectorType.FTP:
case ConnectorType.SNMP:
case ConnectorType.REST:
case ConnectorType.REQUEST:
return params.methodFilter;
case ConnectorType.MODBUS:
return params.tag;
case ConnectorType.BACNET:
case ConnectorType.CAN:
case ConnectorType.OPCUA:
case ConnectorType.OPCUA_ASYNCIO:
return params.method;
case ConnectorType.BLE:
case ConnectorType.OCPP:
case ConnectorType.SOCKET:
case ConnectorType.XMPP:
return params.methodRPC;
default:
return params.command;
} }
this.dialog.open<JsonObjectEditDialogComponent, JsonObjectEditDialogData, object>(JsonObjectEditDialogComponent, { }
saveTemplate() {
this.dialog.open<GatewayServiceRPCConnectorTemplateDialogComponent, SaveRPCTemplateData>
(GatewayServiceRPCConnectorTemplateDialogComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {config: this.commandForm.value.params, templates: this.templates}
jsonValue: JSON.parse(this.commandForm.get('params').value),
required: true
}
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res) => { (res) => {
if (res) { const templateAttribute: RPCTemplate = {
this.commandForm.get('params').setValue(JSON.stringify(res)); name: res,
config: this.commandForm.value.params
} }
const templatesArray = this.templates;
const existingIndex = templatesArray.findIndex(template=>{
return template.name == templateAttribute.name;
})
if (existingIndex > -1 ){
templatesArray.splice(existingIndex, 1)
}
templatesArray.push(templateAttribute)
const key = `${this.connectorType}_template`;
this.attributeService.saveEntityAttributes(
{
id: this.ctx.defaultSubscription.targetDeviceId,
entityType: EntityType.DEVICE
}
, AttributeScope.SERVER_SCOPE, [{
key,
value: templatesArray
}]).subscribe(() => {
})
} }
); );
} }
useTemplate($event) {
this.commandForm.get('params').patchValue($event.config);
}
private updateTemplates() {
this.templates = this.subscription.data[0].data[0][1].length ?
JSON.parse(this.subscription.data[0].data[0][1]) : [];
if (this.templates.length && this.commandForm.get('params').value == "{}") {
this.commandForm.get('params').patchValue(this.templates[0].config);
}
this.cd.detectChanges();
}
private onDataUpdateError(e: any) {
const exceptionData = this.utils.parseException(e);
let errorText = exceptionData.name;
if (exceptionData.message) {
errorText += ': ' + exceptionData.message;
}
console.error(errorText);
}
} }

View File

@ -261,10 +261,17 @@ export enum SocketEncodings {
} }
export interface RPCTemplate { export interface RPCTemplate {
name: string; name?: string;
config: { config: RPCTemplateConfig;
[key: string]: any; }
};
export interface RPCTemplateConfig {
[key: string]: any;
}
export interface SaveRPCTemplateData {
config: RPCTemplateConfig,
templates: Array<RPCTemplate>
} }
export interface LogLink { export interface LogLink {

View File

@ -74,6 +74,9 @@ import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards
import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component'; import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component';
import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget.component'; import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget.component';
import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/doughnut-widget.component'; import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/doughnut-widget.component';
import {
GatewayServiceRPCConnectorTemplateDialogComponent
} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-template-dialog';
@NgModule({ @NgModule({
declarations: declarations:
@ -107,6 +110,7 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough
DeviceGatewayCommandComponent, DeviceGatewayCommandComponent,
GatewayConfigurationComponent, GatewayConfigurationComponent,
GatewayRemoteConfigurationDialogComponent, GatewayRemoteConfigurationDialogComponent,
GatewayServiceRPCConnectorTemplateDialogComponent,
ValueCardWidgetComponent, ValueCardWidgetComponent,
AggregatedValueCardWidgetComponent, AggregatedValueCardWidgetComponent,
CountWidgetComponent, CountWidgetComponent,
@ -154,6 +158,7 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough
DeviceGatewayCommandComponent, DeviceGatewayCommandComponent,
GatewayConfigurationComponent, GatewayConfigurationComponent,
GatewayRemoteConfigurationDialogComponent, GatewayRemoteConfigurationDialogComponent,
GatewayServiceRPCConnectorTemplateDialogComponent,
ValueCardWidgetComponent, ValueCardWidgetComponent,
AggregatedValueCardWidgetComponent, AggregatedValueCardWidgetComponent,
CountWidgetComponent, CountWidgetComponent,

View File

@ -2776,54 +2776,54 @@
"rpc": { "rpc": {
"title": "{{type}} Connector RPC parameters", "title": "{{type}} Connector RPC parameters",
"templates-title": "Connector RPC Templates", "templates-title": "Connector RPC Templates",
"method-filter": "Method filter", "methodFilter": "Method filter",
"request-topic-expression": "Request topic expression", "requestTopicExpression": "Request topic expression",
"response-topic-expression": "Response topic expression", "responseTopicExpression": "Response topic expression",
"response-timeout": "Response Time", "responseTimeout": "Response Time",
"value-expression": "Value Expression", "valueExpression": "Value Expression",
"tag": "Tag", "tag": "Tag",
"type": "Type", "type": "Type",
"function-code": "Function Code", "functionCode": "Function Code",
"objects-count": "Objects Count", "objectsCount": "Objects Count",
"address": "Address", "address": "Address",
"method": "Method", "method": "Method",
"request-type": "Request Type", "requestType": "Request Type",
"request-timeout": "Request Timeout", "requestTimeout": "Request Timeout",
"object-type": "Object type", "objectType": "Object type",
"identifier": "Identifier", "identifier": "Identifier",
"property-id": "Property ID", "propertyId": "Property ID",
"method-rpc": "Method RPC name", "methodRPC": "Method RPC name",
"with-response": "With Response", "withResponse": "With Response",
"characteristic-uuid": "Characteristic UUID", "characteristicUUID": "Characteristic UUID",
"method-processing": "Method Processing", "methodProcessing": "Method Processing",
"node-id": "Node ID", "nodeID": "Node ID",
"is-extended-id": "Is Extended ID", "isExtendedID": "Is Extended ID",
"is-fd": "Is FD", "isFD": "Is FD",
"bitrate-switch": "Bitrate Switch", "bitrateSwitch": "Bitrate Switch",
"data-in-hex": "Data In HEX", "dataInHEX": "Data In HEX",
"data-length": "Data Length", "dataLength": "Data Length",
"data-byte-order": "Data Byte Order", "dataByteorder": "Data Byte Order",
"data-before": "Data Before", "dataBefore": "Data Before",
"data-after": "Data After", "dataAfter": "Data After",
"data-expression": "Data Expression", "dataExpression": "Data Expression",
"encoding": "Encoding", "encoding": "Encoding",
"oid": "OID", "oid": "OID",
"add-oid": "Add OID", "add-oid": "Add OID",
"add-header": "Add header", "add-header": "Add header",
"add-security": "Add security", "add-security": "Add security",
"remove": "Remove", "remove": "Remove",
"request-filter": "Request Filter", "requestFilter": "Request Filter",
"request-url": "Request URL Expression", "requestUrlExpression": "Request URL Expression",
"http-method": "HTTP Method", "HTTPMethod": "HTTP Method",
"timeout": "Timeout", "timeout": "Timeout",
"tries": "Tries", "tries": "Tries",
"http-headers": "HTTP Headers", "httpHeaders": "HTTP Headers",
"header-name": "Header name", "header-name": "Header name",
"security-name": "Security name", "security-name": "Security name",
"value": "Value", "value": "Value",
"security": "Security", "security": "Security",
"response-value-expression": "Response Value Expression", "responseValueExpression": "Response Value Expression",
"request-value-expression": "Request Value Expression", "requestValueExpression": "Request Value Expression",
"arguments": "Arguments", "arguments": "Arguments",
"add-argument": "Add argument", "add-argument": "Add argument",
"write-property": "Write property", "write-property": "Write property",
@ -2843,7 +2843,12 @@
"multi-get": "Multiget", "multi-get": "Multiget",
"get-next": "Get next", "get-next": "Get next",
"bulk-get": "Bulk get", "bulk-get": "Bulk get",
"walk": "Walk" "walk": "Walk",
"save-template": "Save template",
"template-name": "Template name",
"template-name-required": "Template name is required.",
"template-name-duplicate": "The name is already used."
}, },
"other": "Other", "other": "Other",