Browse Source

RPC connector templates

pull/10011/head
dudnikmaksim 3 years ago
parent
commit
bccb2e7e8c
  1. 7
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts
  2. 60
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html
  3. 79
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss
  4. 81
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts
  5. 531
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html
  6. 85
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss
  7. 272
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts
  8. 78
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html
  9. 23
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss
  10. 21
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts
  11. 175
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts
  12. 92
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  13. 74
      ui-ngx/src/assets/locale/locale.constant-en_US.json

7
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts

@ -40,6 +40,7 @@ import { DatasourceType, widgetType } from '@shared/models/widget.models';
import { UtilsService } from '@core/services/utils.service';
import { EntityType } from '@shared/models/entity-type.models';
import {
ConnectorType,
GatewayConnector,
GatewayConnectorDefaultTypesTranslates,
GatewayLogLevel
@ -198,10 +199,10 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
saveConnector(): void {
const value = this.connectorForm.value;
value.configuration = camelCase(value.name) + '.json';
if (value.type !== 'grpc') {
if (value.type !== ConnectorType.GRPC) {
delete value.key;
}
if (value.type !== 'custom') {
if (value.type !== ConnectorType.CUSTOM) {
delete value.class;
}
value.ts = new Date().getTime();
@ -309,7 +310,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
private clearOutConnectorForm(): void {
this.connectorForm.setValue({
name: '',
type: 'mqtt',
type: ConnectorType.MQTT,
logLevel: GatewayLogLevel.INFO,
key: 'auto',
class: '',

60
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html

@ -0,0 +1,60 @@
<!--
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.
-->
<div class="mat-subtitle-1 title">{{ 'gateway.rpc.templates-title' | translate }}</div>
<mat-expansion-panel hideToggle *ngFor="let template of rpcTemplates">
<mat-expansion-panel-header>
<mat-panel-title>
{{template.name}}
</mat-panel-title>
<mat-panel-description>
<button mat-icon-button matTooltip="Delete" (click)="deleteTemplate($event)">
<mat-icon class="material-icons">delete</mat-icon>
</button>
<button mat-icon-button matTooltip="Copy" (click)="copyTemplate($event)">
<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>
</button>
</mat-panel-description>
</mat-expansion-panel-header>
<ng-container
*ngFor="let config of template.config | keyvalue : originalOrder"
[ngTemplateOutlet]="RPCTemplateRef"
[ngTemplateOutletContext]="{ $implicit: config, padding: false }">
</ng-container>
<ng-template #RPCTemplateRef let-config let-padding='padding'>
<div [fxLayout]="isObject(config.value) ? 'column': 'row'"
[fxLayoutAlign]="!isObject(config.value) ? 'space-between center' : ''"
[ngStyle]="{'padding-left': padding ? '16px': '0'}">
<div class="template-key">{{config.key}}</div>
<div *ngIf="!isObject(config.value) else RPCObjectRow"
[ngClass]="{'boolean-true': config.value === true,
'boolean-false': config.value === false }">
{{config.value}}</div>
<ng-template #RPCObjectRow>
<ng-container
*ngFor="let subConfig of config.value | keyvalue : originalOrder"
[ngTemplateOutlet]="RPCTemplateRef"
[ngTemplateOutletContext]="{ $implicit: subConfig, padding: true }">
</ng-container>
</ng-template>
</div>
</ng-template>
</mat-expansion-panel>

79
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss

@ -0,0 +1,79 @@
/**
* 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.
*/
:host {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 0;
.title {
font-weight: 500;
}
.template-key {
color: rgba(0, 0, 0, 0.38);
height: 32px;
line-height: 32px;
}
.boolean-true, .boolean-false {
border-radius: 3px;
height: 32px;
line-height: 32px;
padding: 0 12px;
width: fit-content;
font-size: 14px;
text-transform: capitalize;
}
.boolean-false {
color: #d12730;
background-color: rgba(209, 39, 48, 0.08);
}
.boolean-true {
color: #198038;
background-color: rgba(25, 128, 56, 0.08);
}
.mat-expansion-panel-header-description {
flex-direction: row-reverse;
align-items: center;
margin-right: 0;
& > mat-icon {
margin-left: 15px;
color: rgba(0, 0, 0, 0.38);
}
}
.mat-expansion-panel-header {
padding: 0 0 0 12px;
&.mat-expansion-panel-header.mat-expanded {
height: 48px;
}
.mat-content.mat-content-hide-toggle {
margin-right: 0;
}
}
}

81
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts

@ -0,0 +1,81 @@
///
/// 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, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ConnectorType, RPCTemplate } from '@home/components/widget/lib/gateway/gateway-widget.models';
import { TranslateService } from '@ngx-translate/core';
import { KeyValue } from '@angular/common';
@Component({
selector: 'tb-gateway-service-rpc-connector-templates',
templateUrl: './gateway-service-rpc-connector-templates.component.html',
styleUrls: ['./gateway-service-rpc-connector-templates.component.scss']
})
export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit {
@Input()
connectorType: ConnectorType;
@Output()
saveTemplate: EventEmitter<any> = new EventEmitter();
rpcTemplates: Array<RPCTemplate> = [];
constructor(private translate: TranslateService) {
this.rpcTemplates.push(
{
name: 'Test Template',
config: {
fieldString: 'string',
fieldNumber: 666,
fieldBool: true,
fieldArray: [111, 222, 333, "String", "444"],
fieldObj: {
subKey1: 'dasd',
subKey2: 666,
}
}
}
)
}
ngOnInit() {
}
public useTemplate($event: Event): void {
$event.stopPropagation();
console.log("useTemplate")
}
public copyTemplate($event: Event): void {
$event.stopPropagation();
console.log("copyTemplate")
}
public deleteTemplate($event: Event): void {
$event.stopPropagation();
console.log("deleteTemplate")
}
public originalOrder = (a: KeyValue<string, any>, b: KeyValue<string, any>): number => {
return 0;
}
public isObject(value: any) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
}

531
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html

@ -0,0 +1,531 @@
<!--
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.
-->
<div fxLayout="column" class="command-form" [formGroup]="commandForm">
<div 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.method-filter' | translate }}</mat-label>
<input matInput formControlName="methodFilter"
placeholder="echo"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.request-topic-expression' | translate }}</mat-label>
<input matInput formControlName="requestTopicExpression"
placeholder="sensor/${deviceName}/request/${methodName}/${requestId}"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.response-topic-expression' | translate }}</mat-label>
<input matInput formControlName="responseTopicExpression"
placeholder="sensor/${deviceName}/response/${methodName}/${requestId}"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.response-timeout' | 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.value-expression' | translate }}</mat-label>
<input matInput formControlName="valueExpression"
placeholder="${params}"/>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.MODBUS">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.tag' | translate }}</mat-label>
<input matInput formControlName="tag" placeholder="setValue"/>
</mat-form-field>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label>{{ 'gateway.rpc.type' | translate }}</mat-label>
<mat-select formControlName="type">
<mat-option *ngFor="let type of modbusCommandTypes" [value]="type">
{{ type }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50" class="mat-block">
<mat-label>{{ 'gateway.rpc.function-code' | translate }}</mat-label>
<mat-select formControlName="functionCode">
<mat-option *ngFor="let code of codesArray" [value]="code">
{{ code }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.address' | translate }}</mat-label>
<input matInput formControlName="address" type="number" min="0"
placeholder="1" step="1"/>
</mat-form-field>
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.objects-count' | translate }}</mat-label>
<input matInput formControlName="objectsCount" type="number" min="0"
placeholder="31" step="1"/>
</mat-form-field>
</div>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.BACNET">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label>
<input matInput formControlName="method" placeholder="set_state"/>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.request-type' | translate }}</mat-label>
<mat-select formControlName="requestType">
<mat-option *ngFor="let type of bACnetRequestTypes" [value]="type">
{{bACnetRequestTypesTranslates.get(type) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.request-timeout' | translate }}</mat-label>
<input matInput formControlName="requestTimeout" type="number"
min="10" step="1" placeholder="1000"/>
</mat-form-field>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label>{{ 'gateway.rpc.object-type' | translate }}</mat-label>
<mat-select formControlName="objectType">
<mat-option *ngFor="let type of bACnetObjectTypes" [value]="type">
{{bACnetObjectTypesTranslates.get(type) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.identifier' | translate }}</mat-label>
<input matInput formControlName="identifier" type="number"
min="1" step="1" placeholder="1"/>
</mat-form-field>
</div>
<mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.property-id' | translate }}</mat-label>
<input matInput formControlName="propertyId" placeholder="presentValue"/>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.BLE">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label>
<input matInput formControlName="methodRPC" placeholder="rpcMethod1"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.characteristic-uuid' | translate }}</mat-label>
<input matInput formControlName="characteristicUUID" placeholder="00002A00-0000-1000-8000-00805F9B34FB"/>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.method-processing' | translate }}</mat-label>
<mat-select formControlName="methodProcessing">
<mat-option *ngFor="let type of bLEMethods" [value]="type">
{{bLEMethodsTranslates.get(type) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle class="mat-slide" formControlName="withResponse">
{{ 'gateway.rpc.with-response' | translate }}
</mat-slide-toggle>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.CAN">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label>
<input matInput formControlName="method" placeholder="sendSameData"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.node-id' | translate }}</mat-label>
<input matInput formControlName="nodeID" type="number" placeholder="4" min="0" step="1"/>
</mat-form-field>
<mat-slide-toggle class="mat-slide margin" formControlName="isExtendedID">
{{ 'gateway.rpc.is-extended-id' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide margin" formControlName="isFD">
{{ 'gateway.rpc.is-fd' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide margin" formControlName="bitrateSwitch">
{{ 'gateway.rpc.bitrate-switch' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.data-length' | translate }}</mat-label>
<input matInput formControlName="dataLength" type="number" placeholder="2" min="1" step="1"/>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex="50">
<mat-label>{{ 'gateway.rpc.data-byte-order' | translate }}</mat-label>
<mat-select formControlName="dataByteorder">
<mat-option *ngFor="let order of cANByteOrders" [value]="order">
{{ order | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.data-before' | translate }}</mat-label>
<input matInput formControlName="dataBefore" placeholder="00AA"/>
</mat-form-field>
<mat-form-field fxFlex="50">
<mat-label>{{ 'gateway.rpc.data-after' | translate }}</mat-label>
<input matInput formControlName="dataAfter" placeholder="0102"/>
</mat-form-field>
</div>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.data-in-hex' | translate }}</mat-label>
<input matInput formControlName="dataInHEX"
placeholder="aa bb cc dd ee ff aa bb aa bb cc d ee ff"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.data-expression' | translate }}</mat-label>
<input matInput formControlName="dataExpression"
placeholder="userSpeed if maxAllowedSpeed > userSpeed else maxAllowedSpeed"/>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.FTP">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-filter' | translate }}</mat-label>
<input matInput formControlName="methodFilter" placeholder="read"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" placeholder="${params}"/>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.OCPP">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label>
<input matInput formControlName="methodRPC" placeholder="rpc1"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" placeholder="${params}"/>
</mat-form-field>
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse">
{{ 'gateway.rpc.with-response' | translate }}
</mat-slide-toggle>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.SOCKET">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label>
<input matInput formControlName="methodRPC" placeholder="rpcMethod1"/>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.method-processing' | translate }}</mat-label>
<mat-select formControlName="methodProcessing">
<mat-option *ngFor="let method of socketMethodProcessings" [value]="method">
{{ SocketMethodProcessingsTranslates.get(method) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.encoding' | translate }}</mat-label>
<mat-select formControlName="encoding">
<mat-option *ngFor="let encoding of socketEncodings" [value]="encoding">
{{ encoding }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse">
{{ 'gateway.rpc.with-response' | translate }}
</mat-slide-toggle>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.XMPP">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-rpc' | translate }}</mat-label>
<input matInput formControlName="methodRPC" placeholder="rpc1"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" placeholder="${params}"/>
</mat-form-field>
<mat-slide-toggle class="mat-slide margin" formControlName="withResponse">
{{ 'gateway.rpc.with-response' | translate }}
</mat-slide-toggle>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.SNMP">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.request-filter' | translate }}</mat-label>
<input matInput formControlName="requestFilter" placeholder="setData"/>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label>
<mat-select formControlName="method">
<mat-option *ngFor="let method of sNMPMethods" [value]="method">
{{ SNMPMethodsTranslations.get(method) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="oid">
<span class="fields-label">{{ 'gateway.rpc.oids' | translate }}*</span>
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"
*ngFor="let control of getFormArrayControls('oid'); let i = index">
<mat-form-field appearance="outline" fxFlex>
<input matInput [formControl]="control"/>
</mat-form-field>
<mat-icon style="cursor:pointer;"
fxFlex="30px"
(click)="removeSNMPoid(i)"
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete
</mat-icon>
</div>
<button mat-raised-button
fxFlexAlign="start"
(click)="addSNMPoid()">
{{ 'gateway.rpc.add-oid' | translate }}
</button>
</fieldset>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.REST">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-filter' | translate }}</mat-label>
<input matInput formControlName="methodFilter" placeholder="post_attributes"/>
</mat-form-field>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field class="mat-block" fxFlex="33">
<mat-label>{{ 'gateway.rpc.http-method' | translate }}</mat-label>
<mat-select formControlName="HTTPMethod">
<mat-option *ngFor="let method of hTTPMethods" [value]="method">
{{ method }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'gateway.rpc.request-url' | translate }}</mat-label>
<input matInput formControlName="requestUrlExpression"
placeholder="http://127.0.0.1:5000/my_devices"/>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="33">
<mat-label>{{ 'gateway.rpc.response-timeout' | translate }}</mat-label>
<input matInput formControlName="responseTimeout" type="number"
step="1" min="10" placeholder="10"/>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'gateway.rpc.timeout' | translate }}</mat-label>
<input matInput formControlName="timeout" type="number"
step="1" min="10" placeholder="1000"/>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'gateway.rpc.tries' | translate }}</mat-label>
<input matInput formControlName="tries" type="number"
step="1" min="1" placeholder="3"/>
</mat-form-field>
</div>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.value-expression' | translate }}</mat-label>
<input matInput formControlName="valueExpression" placeholder="${params}"/>
</mat-form-field>
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="httpHeaders">
<span class="fields-label">{{ 'gateway.rpc.http-headers' | translate }}</span>
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('httpHeaders').length">
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center">
<span fxFlex class="title">{{ 'gateway.rpc.header-name' | translate }}</span>
<span fxFlex class="title">{{ 'gateway.rpc.value' | translate }}</span>
<span fxFlex="30px"></span>
</div>
<mat-divider></mat-divider>
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"
*ngFor="let control of getFormArrayControls('httpHeaders'); let i = index">
<ng-container [formGroupName]="i">
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="headerName"/>
</mat-form-field>
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="value" placeholder="application/json"/>
</mat-form-field>
<mat-icon style="cursor:pointer;"
fxFlex="30px"
(click)="removeHTTPHeader(i)"
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete
</mat-icon>
</ng-container>
</div>
</div>
<button mat-raised-button
fxFlexAlign="start"
(click)="addHTTPHeader()">
{{ 'gateway.rpc.add-header' | translate }}
</button>
</fieldset>
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="security">
<span class="fields-label">{{ 'gateway.rpc.security' | translate }}</span>
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('security').length">
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center">
<span fxFlex class="title">{{ 'gateway.rpc.security-name' | translate }}</span>
<span fxFlex class="title">{{ 'gateway.rpc.value' | translate }}</span>
<span fxFlex="30px"></span>
</div>
<mat-divider></mat-divider>
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"
*ngFor="let control of getFormArrayControls('security'); let i = index">
<ng-container [formGroupName]="i">
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="securityName"/>
</mat-form-field>
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="value" placeholder="anonymous"/>
</mat-form-field>
<mat-icon style="cursor:pointer;"
fxFlex="30px"
(click)="removeHTTPSecurity(i)"
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete
</mat-icon>
</ng-container>
</div>
</div>
<button mat-raised-button
fxFlexAlign="start"
(click)="addHTTPSecurity()">
{{ 'gateway.rpc.add-security' | translate }}
</button>
</fieldset>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.REQUEST">
<mat-form-field>
<mat-label>{{ 'gateway.rpc.method-filter' | translate }}</mat-label>
<input matInput formControlName="methodFilter" placeholder="echo"/>
</mat-form-field>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field class="mat-block" fxFlex="33">
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label>
<mat-select formControlName="httpMethod">
<mat-option *ngFor="let method of hTTPMethods" [value]="method">
{{ method }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'gateway.rpc.request-url' | translate }}</mat-label>
<input matInput formControlName="requestUrlExpression" placeholder="http://127.0.0.1:5000/my_devices"/>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex="33">
<mat-label>{{ 'gateway.rpc.response-timeout' | translate }}</mat-label>
<input matInput formControlName="responseTimeout" type="number"
step="1" min="10" placeholder="10"/>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'gateway.rpc.timeout' | translate }}</mat-label>
<input matInput formControlName="timeout" type="number"
step="1" min="10" placeholder="10"/>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'gateway.rpc.tries' | translate }}</mat-label>
<input matInput formControlName="tries" type="number"
step="1" min="1" placeholder="1"/>
</mat-form-field>
</div>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.request-value-expression' | translate }}</mat-label>
<input matInput formControlName="requestValueExpression" placeholder="${params}"/>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'gateway.rpc.response-value-expression' | translate }}</mat-label>
<input matInput formControlName="responseValueExpression" placeholder="${temp}"/>
</mat-form-field>
<fieldset class="fields border" fxLayout="column" fxLayoutGap="10px" formArrayName="httpHeaders">
<span class="fields-label">{{ 'gateway.rpc.http-headers' | translate }}</span>
<div class="border" fxLayout="column" fxLayoutGap="10px" *ngIf="getFormArrayControls('httpHeaders').length">
<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center">
<span fxFlex class="title">{{ 'gateway.rpc.header-name' | translate }}</span>
<span fxFlex class="title">{{ 'gateway.rpc.value' | translate }}</span>
<span fxFlex="30px"></span>
</div>
<mat-divider></mat-divider>
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center"
*ngFor="let control of getFormArrayControls('httpHeaders'); let i = index">
<ng-container [formGroupName]="i">
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="headerName" placeholder="{{ 'gateway.rpc.set' | translate }}"/>
</mat-form-field>
<mat-form-field appearance="outline" fxFlex>
<input matInput formControlName="value"/>
</mat-form-field>
<mat-icon style="cursor:pointer;"
fxFlex="30px"
(click)="removeHTTPHeader(i)"
matTooltip="{{ 'gateway.rpc.remove' | translate }}">delete
</mat-icon>
</ng-container>
</div>
</div>
<button mat-raised-button
fxFlexAlign="start"
(click)="addHTTPHeader()">
{{ 'gateway.rpc.add-header' | translate }}
</button>
</fieldset>
</ng-template>
<ng-template [ngSwitchCase]="ConnectorType.OPCUA_ASYNCIO">
<ng-container *ngTemplateOutlet="OPCUAForm"></ng-container>
</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"/>
</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-container>
</ng-template>
<div class="template-actions" fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="10px">
<button mat-raised-button
(click)="sendCommand.emit()"
[disabled]="commandForm.invalid">
{{ 'gateway.rpc-command-save-template' | translate }}
</button>
<button mat-raised-button
color="primary"
(click)="sendCommand.emit()"
[disabled]="commandForm.invalid">
{{ 'gateway.rpc-command-send' | translate }}
</button>
</div>
</div>

85
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.scss

@ -0,0 +1,85 @@
/**
* 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.
*/
:host {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 0;
.title {
font-weight: 500;
}
.command-form {
flex-wrap: nowrap;
& > button {
margin-top: 10px;
}
}
.mat-mdc-slide-toggle.margin {
margin-bottom: 10px;
margin-left: 10px;
}
.fields {
::ng-deep .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix {
padding-top: 6px;
padding-bottom: 6px;
min-height: auto;
}
::ng-deep .mat-mdc-form-field-subscript-wrapper {
display: none;
}
::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__leading,
::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__notch,
::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing {
border-color: #e0e0e0;
}
.fields-label {
font-weight: 500;
}
}
.border {
padding: 16px;
margin-bottom: 10px;
box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%);
border: solid 1px #e0e0e0;
border-radius: 4px;
.title {
color: rgba(0, 0, 0, .54);
}
.mat-icon {
color: rgba(0, 0, 0, .38);
}
.mat-divider {
margin-left: -16px;
margin-right: -16px;
margin-bottom: 16px;
}
}
}

272
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts

@ -0,0 +1,272 @@
///
/// 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, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import {
BACnetObjectTypes, BACnetObjectTypesTranslates,
BACnetRequestTypes, BACnetRequestTypesTranslates, BLEMethods, BLEMethodsTranslates,
CANByteOrders,
ConnectorType, GatewayConnectorDefaultTypesTranslates, HTTPMethods,
ModbusCommandTypes,
RPCCommand,
SNMPMethods, SNMPMethodsTranslations,
SocketEncodings,
SocketMethodProcessings, SocketMethodProcessingsTranslates
} from '@home/components/widget/lib/gateway/gateway-widget.models';
import { TranslateService } from "@ngx-translate/core";
@Component({
selector: 'tb-gateway-service-rpc-connector',
templateUrl: './gateway-service-rpc-connector.component.html',
styleUrls: ['./gateway-service-rpc-connector.component.scss']
})
export class GatewayServiceRPCConnectorComponent implements OnInit {
@Input()
connectorType: ConnectorType;
@Output()
sendCommand: EventEmitter<RPCCommand> = new EventEmitter();
commandForm: FormGroup;
codesArray: Array<number> = [1, 2, 3, 4, 5, 6, 15, 16];
readonly ConnectorType = ConnectorType;
modbusCommandTypes = Object.values(ModbusCommandTypes) as ModbusCommandTypes[];
bACnetRequestTypes = Object.values(BACnetRequestTypes) as BACnetRequestTypes[];
bACnetObjectTypes = Object.values(BACnetObjectTypes) as BACnetObjectTypes[];
bLEMethods = Object.values(BLEMethods) as BLEMethods[];
cANByteOrders = Object.values(CANByteOrders) as CANByteOrders[];
socketMethodProcessings = Object.values(SocketMethodProcessings) as SocketMethodProcessings[];
socketEncodings = Object.values(SocketEncodings) as SocketEncodings[];
sNMPMethods = Object.values(SNMPMethods) as SNMPMethods[];
hTTPMethods = Object.values(HTTPMethods) as HTTPMethods[];
bACnetRequestTypesTranslates = BACnetRequestTypesTranslates;
bACnetObjectTypesTranslates = BACnetObjectTypesTranslates;
bLEMethodsTranslates = BLEMethodsTranslates;
SocketMethodProcessingsTranslates = SocketMethodProcessingsTranslates;
SNMPMethodsTranslations = SNMPMethodsTranslations;
gatewayConnectorDefaultTypesTranslates = GatewayConnectorDefaultTypesTranslates;
urlPattern = new RegExp(
'^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR IP (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$', // fragment locator
'i'
);
constructor(private fb: FormBuilder,
private translate: TranslateService) {
}
ngOnInit() {
this.commandForm = this.connectorParamsFormGroupByType(this.connectorType)
}
connectorParamsFormGroupByType(type: ConnectorType): FormGroup {
let formGroup: FormGroup;
switch (type) {
case ConnectorType.MQTT:
formGroup = this.fb.group({
methodFilter: [null, [Validators.required]],
requestTopicExpression: [null, [Validators.required]],
responseTopicExpression: [null, []],
responseTimeout: [null, [Validators.min(10), Validators.pattern("^[0-9]*$")]],
valueExpression: [null, [Validators.required]],
})
break;
case ConnectorType.MODBUS:
formGroup = this.fb.group({
tag: [null, [Validators.required]],
type: [null, [Validators.required]],
functionCode: [null, [Validators.required]],
address: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]],
objectsCount: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]]
})
break;
case ConnectorType.BACNET:
formGroup = this.fb.group({
method: [null, [Validators.required]],
requestType: [null, [Validators.required]],
requestTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]],
objectType: [null, []],
identifier: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]],
propertyId: [null, [Validators.required]]
})
break;
case ConnectorType.BLE:
formGroup = this.fb.group({
methodRPC: [null, [Validators.required]],
characteristicUUID: [null, [Validators.required]],
methodProcessing: [null, [Validators.required]],
withResponse: [false, []]
})
break;
case ConnectorType.CAN:
formGroup = this.fb.group({
method: [null, [Validators.required]],
nodeID: [null, [Validators.required, Validators.min(0), Validators.pattern("^[0-9]*$")]],
isExtendedID: [false, []],
isFD: [false, []],
bitrateSwitch: [false, []],
dataLength: [null, [Validators.min(1), Validators.pattern("^[0-9]*$")]],
dataByteorder: [null, []],
dataBefore: [null, []],
dataAfter: [null, []],
dataInHEX: [null, []],
dataExpression: [null, []]
})
break;
case ConnectorType.FTP:
formGroup = this.fb.group({
methodFilter: [null, [Validators.required]],
valueExpression: [null, [Validators.required]]
})
break;
case ConnectorType.OCPP:
formGroup = this.fb.group({
methodRPC: [null, [Validators.required]],
valueExpression: [null, [Validators.required]],
withResponse: [false, []]
})
break;
case ConnectorType.SOCKET:
formGroup = this.fb.group({
methodRPC: [null, [Validators.required]],
methodProcessing: [null, [Validators.required]],
encoding: [null, [Validators.required]],
withResponse: [false, []]
})
break;
case ConnectorType.XMPP:
formGroup = this.fb.group({
methodRPC: [null, [Validators.required]],
valueExpression: [null, [Validators.required]],
withResponse: [false, []]
})
break;
case ConnectorType.SNMP:
formGroup = this.fb.group({
requestFilter: [null, [Validators.required]],
method: [null, [Validators.required]],
oid: this.fb.array([], [Validators.required])
})
break;
case ConnectorType.REST:
formGroup = this.fb.group({
methodFilter: [null, [Validators.required]],
HTTPMethod: [null, [Validators.required]],
requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]],
responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]],
timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]],
tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]],
valueExpression: [null, [Validators.required]],
httpHeaders: this.fb.array([]),
security: this.fb.array([])
})
break;
case ConnectorType.REQUEST:
formGroup = this.fb.group({
methodFilter: [null, [Validators.required]],
httpMethod: [null, [Validators.required]],
requestUrlExpression: [null, [Validators.required, Validators.pattern(this.urlPattern)]],
responseTimeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]],
timeout: [null, [Validators.required, Validators.min(10), Validators.pattern("^[0-9]*$")]],
tries: [null, [Validators.required, Validators.min(1), Validators.pattern("^[0-9]*$")]],
requestValueExpression: [null, [Validators.required]],
responseValueExpression: [null, []],
httpHeaders: this.fb.array([]),
})
break;
case ConnectorType.OPCUA:
case ConnectorType.OPCUA_ASYNCIO:
formGroup = this.fb.group({
method: [null, [Validators.required]],
arguments: this.fb.array([]),
})
}
return formGroup;
}
addSNMPoid(value: string = null) {
const oidsFA = this.commandForm.get('oid') as FormArray;
if (oidsFA) {
oidsFA.push(this.fb.control(value, [Validators.required]));
}
}
removeSNMPoid(index: number) {
const oidsFA = this.commandForm.get('oid') as FormArray;
oidsFA.removeAt(index);
}
addHTTPHeader(value: { headerName: string, value: string } = {headerName: null, value: null}) {
const headerFA = this.commandForm.get('httpHeaders') as FormArray;
const formGroup = this.fb.group({
headerName: [value.headerName, [Validators.required]],
value: [value.value, [Validators.required]]
})
if (headerFA) {
headerFA.push(formGroup);
}
}
removeHTTPHeader(index: number) {
const oidsFA = this.commandForm.get('httpHeaders') as FormArray;
oidsFA.removeAt(index);
}
addHTTPSecurity(value: { securityName: string, value: string } = {securityName: null, value: null}) {
const securityFA = this.commandForm.get('security') as FormArray;
const formGroup = this.fb.group({
securityName: [value.securityName, [Validators.required]],
value: [value.value, [Validators.required]]
})
if (securityFA) {
securityFA.push(formGroup);
}
}
removeHTTPSecurity(index: number) {
const oidsFA = this.commandForm.get('security') as FormArray;
oidsFA.removeAt(index);
}
getFormArrayControls(path: string) {
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));
}
}
removeOCPUAArguments(index: number) {
const oidsFA = this.commandForm.get('arguments') as FormArray;
oidsFA.removeAt(index);
}
}

78
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html

@ -15,51 +15,41 @@
limitations under the License.
-->
<div fxLayout="row" fxLayout.lt-sm="column" class="command-form" fxLayoutGap="10px" [formGroup]="commandForm">
<ng-container *ngIf="!isConnector; else connectorForm">
<mat-form-field>
<mat-label>{{ 'gateway.statistics.command' | translate }}</mat-label>
<mat-select formControlName="command">
<mat-option *ngFor="let command of RPCCommands" [value]="command">
{{ command }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'gateway.statistics.timeout-ms' | translate }}</mat-label>
<input matInput formControlName="time" type="number" min="1"/>
<mat-error *ngIf="commandForm.get('time').hasError('min')">
{{ 'gateway.statistics.timeout-min' | translate }}
</mat-error>
</mat-form-field>
</ng-container>
<ng-template #connectorForm>
<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>
<button mat-raised-button
color="primary"
(click)="sendCommand()"
[disabled]="commandForm.invalid">
{{ 'gateway.rpc-command-send' | translate }}
</button>
</div>
<section class="result-block" [formGroup]="commandForm">
<div fxLayout="column" fxFlex [ngClass]="{'border': isConnector}">
<div fxLayout="row" fxLayout.lt-sm="column" class="command-form" fxLayoutGap="10px" [formGroup]="commandForm">
<ng-container *ngIf="!isConnector; else connectorForm">
<mat-form-field>
<mat-label>{{ 'gateway.statistics.command' | translate }}</mat-label>
<mat-select formControlName="command">
<mat-option *ngFor="let command of RPCCommands" [value]="command">
{{ command }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label>{{ 'gateway.statistics.timeout-ms' | translate }}</mat-label>
<input matInput formControlName="time" type="number" min="1"/>
<mat-error *ngIf="commandForm.get('time').hasError('min')">
{{ 'gateway.statistics.timeout-min' | translate }}
</mat-error>
</mat-form-field>
<button mat-raised-button
color="primary"
(click)="sendCommand()"
[disabled]="commandForm.invalid">
{{ 'gateway.rpc-command-send' | translate }}
</button>
</ng-container>
<ng-template #connectorForm>
<tb-gateway-service-rpc-connector [connectorType]="connectorType" (sendCommand)="sendCommand()"/>
</ng-template>
</div>
<section class="result-block" [formGroup]="commandForm">
<span>{{ 'gateway.rpc-command-result' | translate }}
<div *ngIf="resultTime" class="result-time" fxFlex fxLayout="row" fxLayoutAlign="center center"> <mat-icon
class="material-icons">schedule</mat-icon>
<span>{{ resultTime | date: 'yyyy/MM/dd HH:mm:ss' }}</span> </div></span>
<tb-json-content [contentType]="contentTypes.JSON" readonly="true" formControlName="result"></tb-json-content>
</section>
<tb-json-content [contentType]="contentTypes.JSON" readonly="true" formControlName="result"></tb-json-content>
</section>
</div>
<tb-gateway-service-rpc-connector-templates fxFlex="30" *ngIf="isConnector" class="border"></tb-gateway-service-rpc-connector-templates>

23
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.scss

@ -16,10 +16,20 @@
:host {
width: 100%;
height: 100%;
overflow-x: auto;
overflow: auto;
display: flex;
flex-direction: column;
padding: 0;
flex-direction: row;
padding: 0 5px;
& > * {
height: 100%;
overflow: auto;
}
& > tb-gateway-service-rpc-connector-templates:last-child {
margin-left: 10px;
}
.command-form {
flex-wrap: nowrap;
@ -63,6 +73,13 @@
flex: 1;
}
}
.border {
padding: 16px;
box-shadow: 0 0 0 0 rgb(0 0 0 / 20%), 0 0 0 0 rgb(0 0 0 / 14%), 0 0 0 0 rgb(0 0 0 / 12%);
border: solid 1px #e0e0e0;
border-radius: 4px;
}
}
:host ::ng-deep {

21
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { AfterViewInit, Component, Input } from '@angular/core';
import { OnInit, Component, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { WidgetContext } from '@home/models/widget-component.models';
@ -24,13 +24,14 @@ import {
JsonObjectEditDialogData
} from '@shared/components/dialog/json-object-edit-dialog.component';
import { jsonRequired } from '@shared/components/json-object-edit.component';
import { ConnectorType, RPCCommand } from '@home/components/widget/lib/gateway/gateway-widget.models';
@Component({
selector: 'tb-gateway-service-rpc',
templateUrl: './gateway-service-rpc.component.html',
styleUrls: ['./gateway-service-rpc.component.scss']
})
export class GatewayServiceRPCComponent implements AfterViewInit {
export class GatewayServiceRPCComponent implements OnInit {
@Input()
ctx: WidgetContext;
@ -56,19 +57,19 @@ export class GatewayServiceRPCComponent implements AfterViewInit {
'Reboot'
];
private connectorType: string;
public connectorType: ConnectorType;
constructor(private fb: FormBuilder,
private dialog: MatDialog) {
this.commandForm = this.fb.group({
command: [null,[Validators.required]],
command: [null, [Validators.required]],
time: [60, [Validators.required, Validators.min(1)]],
params: ['{}', [jsonRequired]],
result: [null]
});
}
ngAfterViewInit() {
ngOnInit() {
this.isConnector = this.ctx.settings.isConnector;
if (!this.isConnector) {
this.commandForm.get('command').setValue(this.RPCCommands[0]);
@ -77,17 +78,17 @@ export class GatewayServiceRPCComponent implements AfterViewInit {
}
}
sendCommand() {
sendCommand(value?: RPCCommand) {
this.resultTime = null;
const formValues = this.commandForm.value;
const formValues = value || this.commandForm.value;
const commandPrefix = this.isConnector ? `${this.connectorType}_` : 'gateway_';
this.ctx.controlApi.sendTwoWayCommand(commandPrefix+formValues.command.toLowerCase(), formValues.params,formValues.time).subscribe({
this.ctx.controlApi.sendTwoWayCommand(commandPrefix + formValues.command.toLowerCase(), formValues.params, formValues.time).subscribe({
next: resp => {
this.resultTime = new Date().getTime();
this.resultTime = new Date().getTime();
this.commandForm.get('result').setValue(JSON.stringify(resp))
},
error: error => {
this.resultTime = new Date().getTime();
this.resultTime = new Date().getTime();
console.error(error);
this.commandForm.get('result').setValue(JSON.stringify(error.error));
}

175
ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts

@ -107,27 +107,166 @@ export interface GatewayConnector {
key?: string;
}
export enum ConnectorType {
MQTT = 'mqtt',
MODBUS = 'modbus',
GRPC = 'grpc',
OPCUA = 'opcua',
OPCUA_ASYNCIO = 'opcua_asyncio',
BLE = 'ble',
REQUEST = 'request',
CAN = 'can',
BACNET = 'bacnet',
ODBC = 'odbc',
REST = 'rest',
SNMP = 'snmp',
FTP = 'ftp',
SOCKET = 'socket',
XMPP = 'xmpp',
OCPP = 'ocpp',
CUSTOM = 'custom'
}
export const GatewayConnectorDefaultTypesTranslates = new Map<string, string>([
['mqtt', 'MQTT'],
['modbus', 'MODBUS'],
['grpc', 'GRPC'],
['opcua', 'OPCUA'],
['opcua_asyncio', 'OPCUA ASYNCIO'],
['ble', 'BLE'],
['request', 'REQUEST'],
['can', 'CAN'],
['bacnet', 'BACNET'],
['odbc', 'ODBC'],
['rest', 'REST'],
['snmp', 'SNMP'],
['ftp', 'FTP'],
['socket', 'SOCKET'],
['xmpp', 'XMPP'],
['ocpp', 'OCPP'],
['custom', 'CUSTOM']
export const GatewayConnectorDefaultTypesTranslates = new Map<ConnectorType, string>([
[ConnectorType.MQTT, 'MQTT'],
[ConnectorType.MODBUS, 'MODBUS'],
[ConnectorType.GRPC, 'GRPC'],
[ConnectorType.OPCUA, 'OPCUA'],
[ConnectorType.OPCUA_ASYNCIO, 'OPCUA ASYNCIO'],
[ConnectorType.BLE, 'BLE'],
[ConnectorType.REQUEST, 'REQUEST'],
[ConnectorType.CAN, 'CAN'],
[ConnectorType.BACNET, 'BACNET'],
[ConnectorType.ODBC, 'ODBC'],
[ConnectorType.REST, 'REST'],
[ConnectorType.SNMP, 'SNMP'],
[ConnectorType.FTP, 'FTP'],
[ConnectorType.SOCKET, 'SOCKET'],
[ConnectorType.XMPP, 'XMPP'],
[ConnectorType.OCPP, 'OCPP'],
[ConnectorType.CUSTOM, 'CUSTOM']
]);
export interface RPCCommand {
command: string,
params: any,
time: number
}
export enum ModbusCommandTypes {
Bits = 'bits',
Bit = 'bit',
String = 'string',
Bytes = 'bytes',
Int8 = '8int',
Uint8 = '8uint',
Int16 = '16int',
Uint16 = '16uint',
Float16 = '16float',
Int32 = '32int',
Uint32 = '32uint',
Float32 = '32float',
Int64 = '64int',
Uint64 = '64uint',
Float64 = '64float'
}
export enum BACnetRequestTypes {
WriteProperty = 'writeProperty',
ReadProperty = 'readProperty'
}
export const BACnetRequestTypesTranslates = new Map<BACnetRequestTypes, string>([
[BACnetRequestTypes.WriteProperty, 'gateway.rpc.write-property'],
[BACnetRequestTypes.ReadProperty, "gateway.rpc.read-property"]
])
export enum BACnetObjectTypes {
BinaryOutput = 'binaryOutput',
AnalogOutput = 'analogOutput',
BinaryValue = 'binaryValue'
}
export const BACnetObjectTypesTranslates = new Map<BACnetObjectTypes, string>([
[BACnetObjectTypes.AnalogOutput, 'gateway.rpc.analog-output'],
[BACnetObjectTypes.BinaryOutput, 'gateway.rpc.binary-output'],
[BACnetObjectTypes.BinaryValue, 'gateway.rpc.binary-value'],
])
export enum BLEMethods {
WRITE = 'write',
READ = 'read',
SCAN = 'scan'
}
export const BLEMethodsTranslates = new Map<BLEMethods, string>([
[BLEMethods.WRITE, 'gateway.rpc.write'],
[BLEMethods.READ, 'gateway.rpc.read'],
[BLEMethods.SCAN, 'gateway.rpc.scan'],
])
export enum CANByteOrders {
LITTLE = 'LITTLE',
BIG = 'BIG'
}
export enum SocketMethodProcessings {
WRITE = 'write'
}
export const SocketMethodProcessingsTranslates = new Map<SocketMethodProcessings, string>([
[SocketMethodProcessings.WRITE, 'gateway.rpc.write']
])
export enum SNMPMethods {
SET = 'set',
MULTISET = "multiset",
GET = "get",
BULKWALK = "bulkwalk",
TABLE = "table",
MULTIGET = "multiget",
GETNEXT = "getnext",
BULKGET = "bulkget",
WALKS = "walk"
}
export const SNMPMethodsTranslations = new Map<SNMPMethods, string>([
[SNMPMethods.SET, 'gateway.rpc.set'],
[SNMPMethods.MULTISET, 'gateway.rpc.multiset'],
[SNMPMethods.GET, 'gateway.rpc.get'],
[SNMPMethods.BULKWALK, 'gateway.rpc.bulk-walk'],
[SNMPMethods.TABLE, 'gateway.rpc.table'],
[SNMPMethods.MULTIGET, 'gateway.rpc.multi-get'],
[SNMPMethods.GETNEXT, 'gateway.rpc.get-next'],
[SNMPMethods.BULKGET, 'gateway.rpc.bul-kget'],
[SNMPMethods.WALKS, 'gateway.rpc.walk']
])
export enum HTTPMethods {
CONNECT = 'CONNECT',
DELETE = 'DELETE',
GET = 'GET',
HEAD = 'HEAD',
OPTIONS = 'OPTIONS',
PATCH = 'PATCH',
POST = 'POST',
PUT = 'PUT',
TRACE = 'TRACE'
}
export enum SocketEncodings {
UTF_8 = 'utf-8'
}
export interface RPCTemplate {
name: string;
config: {
[key: string]: any;
};
}
export interface LogLink {
name: string;
key: string;

92
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -22,7 +22,9 @@ import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/displa
import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarm/alarms-table-widget.component';
import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component';
import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entity/entities-hierarchy-widget.component';
import {
EntitiesHierarchyWidgetComponent
} from '@home/components/widget/lib/entity/entities-hierarchy-widget.component';
import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.module';
import {
DateRangeNavigatorPanelComponent,
@ -47,6 +49,12 @@ import { GatewayConnectorComponent } from '@home/components/widget/lib/gateway/g
import { GatewayLogsComponent } from '@home/components/widget/lib/gateway/gateway-logs.component';
import { GatewayStatisticsComponent } from '@home/components/widget/lib/gateway/gateway-statistics.component';
import { GatewayServiceRPCComponent } from '@home/components/widget/lib/gateway/gateway-service-rpc.component';
import {
GatewayServiceRPCConnectorComponent
} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector.component';
import {
GatewayServiceRPCConnectorTemplatesComponent
} from '@home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component';
import { DeviceGatewayCommandComponent } from '@home/components/widget/lib/gateway/device-gateway-command.component';
import { GatewayConfigurationComponent } from '@home/components/widget/lib/gateway/gateway-configuration.component';
import {
@ -94,6 +102,8 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough
GatewayLogsComponent,
GatewayStatisticsComponent,
GatewayServiceRPCComponent,
GatewayServiceRPCConnectorComponent,
GatewayServiceRPCConnectorTemplatesComponent,
DeviceGatewayCommandComponent,
GatewayConfigurationComponent,
GatewayRemoteConfigurationDialogComponent,
@ -115,46 +125,48 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough
HomePageWidgetsModule,
SharedHomeComponentsModule
],
exports: [
EntitiesTableWidgetComponent,
AlarmsTableWidgetComponent,
TimeseriesTableWidgetComponent,
EntitiesHierarchyWidgetComponent,
EdgesOverviewWidgetComponent,
RpcWidgetsModule,
HomePageWidgetsModule,
DateRangeNavigatorWidgetComponent,
JsonInputWidgetComponent,
MultipleInputWidgetComponent,
TripAnimationComponent,
PhotoCameraInputWidgetComponent,
GatewayFormComponent,
NavigationCardsWidgetComponent,
NavigationCardWidgetComponent,
QrCodeWidgetComponent,
MarkdownWidgetComponent,
LegendComponent,
FlotWidgetComponent,
GatewayConnectorComponent,
GatewayLogsComponent,
GatewayStatisticsComponent,
GatewayServiceRPCComponent,
DeviceGatewayCommandComponent,
GatewayConfigurationComponent,
GatewayRemoteConfigurationDialogComponent,
ValueCardWidgetComponent,
AggregatedValueCardWidgetComponent,
CountWidgetComponent,
BatteryLevelWidgetComponent,
WindSpeedDirectionWidgetComponent,
SignalStrengthWidgetComponent,
ValueChartCardWidgetComponent,
ProgressBarWidgetComponent,
LiquidLevelWidgetComponent,
DoughnutWidgetComponent
],
exports: [
EntitiesTableWidgetComponent,
AlarmsTableWidgetComponent,
TimeseriesTableWidgetComponent,
EntitiesHierarchyWidgetComponent,
EdgesOverviewWidgetComponent,
RpcWidgetsModule,
HomePageWidgetsModule,
DateRangeNavigatorWidgetComponent,
JsonInputWidgetComponent,
MultipleInputWidgetComponent,
TripAnimationComponent,
PhotoCameraInputWidgetComponent,
GatewayFormComponent,
NavigationCardsWidgetComponent,
NavigationCardWidgetComponent,
QrCodeWidgetComponent,
MarkdownWidgetComponent,
LegendComponent,
FlotWidgetComponent,
GatewayConnectorComponent,
GatewayLogsComponent,
GatewayServiceRPCConnectorComponent,
GatewayServiceRPCConnectorTemplatesComponent,
GatewayStatisticsComponent,
GatewayServiceRPCComponent,
DeviceGatewayCommandComponent,
GatewayConfigurationComponent,
GatewayRemoteConfigurationDialogComponent,
ValueCardWidgetComponent,
AggregatedValueCardWidgetComponent,
CountWidgetComponent,
BatteryLevelWidgetComponent,
WindSpeedDirectionWidgetComponent,
SignalStrengthWidgetComponent,
ValueChartCardWidgetComponent,
ProgressBarWidgetComponent,
LiquidLevelWidgetComponent,
DoughnutWidgetComponent
],
providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule}
]
})
export class WidgetComponentsModule {

74
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2681,6 +2681,7 @@
"connectors-table-actions": "Actions",
"connectors-table-key": "Key",
"connectors-table-class": "Class",
"rpc-command-save-template": "Save Template",
"rpc-command-send": "Send",
"rpc-command-result": "Response",
"rpc-command-edit-params": "Edit parameters",
@ -2772,6 +2773,79 @@
"remove-entry": "Remove configuration",
"remote-shell": "Remote shell",
"remote-configuration": "Remote Configuration",
"rpc": {
"title": "{{type}} Connector RPC parameters",
"templates-title": "Connector RPC Templates",
"method-filter": "Method filter",
"request-topic-expression": "Request topic expression",
"response-topic-expression": "Response topic expression",
"response-timeout": "Response Time",
"value-expression": "Value Expression",
"tag": "Tag",
"type": "Type",
"function-code": "Function Code",
"objects-count": "Objects Count",
"address": "Address",
"method": "Method",
"request-type": "Request Type",
"request-timeout": "Request Timeout",
"object-type": "Object type",
"identifier": "Identifier",
"property-id": "Property ID",
"method-rpc": "Method RPC name",
"with-response": "With Response",
"characteristic-uuid": "Characteristic UUID",
"method-processing": "Method Processing",
"node-id": "Node ID",
"is-extended-id": "Is Extended ID",
"is-fd": "Is FD",
"bitrate-switch": "Bitrate Switch",
"data-in-hex": "Data In HEX",
"data-length": "Data Length",
"data-byte-order": "Data Byte Order",
"data-before": "Data Before",
"data-after": "Data After",
"data-expression": "Data Expression",
"encoding": "Encoding",
"oid": "OID",
"add-oid": "Add OID",
"add-header": "Add header",
"add-security": "Add security",
"remove": "Remove",
"request-filter": "Request Filter",
"request-url": "Request URL Expression",
"http-method": "HTTP Method",
"timeout": "Timeout",
"tries": "Tries",
"http-headers": "HTTP Headers",
"header-name": "Header name",
"security-name": "Security name",
"value": "Value",
"security": "Security",
"response-value-expression": "Response Value Expression",
"request-value-expression": "Request Value Expression",
"arguments": "Arguments",
"add-argument": "Add argument",
"write-property": "Write property",
"read-property": "Read property",
"analog-output": "Analog output",
"binary-output": "Binary output",
"binary-value": "Binary value",
"write": "Write",
"read": "Read",
"scan": "Scan",
"oids": "OIDS",
"set": "Set",
"multiset": "Multiset",
"get": "Get",
"bulk-walk": "Bulk walk",
"table": "Table",
"multi-get": "Multiget",
"get-next": "Get next",
"bulk-get": "Bulk get",
"walk": "Walk"
},
"other": "Other",
"save-tip": "Save configuration file",
"security-type": "Security type",

Loading…
Cancel
Save