From f48fbc61b4228a838e5a9876699ddc462248ebbe Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 18 Jul 2024 18:18:31 +0300 Subject: [PATCH 1/5] Added extended functionality for building Modbus connector using UI --- .../broker-config-control.component.html | 2 +- .../broker-config-control.component.ts | 15 +- .../mapping-table/mapping-table.component.ts | 39 +-- .../connectors-configuration/modbus/index.ts | 7 + .../modbus-basic-config.component.html | 28 ++ .../modbus-basic-config.component.ts | 130 ++++++++ .../modbus-data-keys-panel.component.html | 147 +++++++++ .../modbus-data-keys-panel.component.scss | 44 +++ .../modbus-data-keys-panel.component.ts | 164 ++++++++++ .../modbus-master-table.component.html | 131 ++++++++ .../modbus-master-table.component.scss | 101 ++++++ .../modbus-master-table.component.ts | 231 +++++++++++++ .../modbus-security-config.component.html | 44 +++ .../modbus-security-config.component.ts | 126 +++++++ .../modbus-slave-config.component.html | 217 +++++++++++++ .../modbus-slave-config.component.ts | 185 +++++++++++ .../modbus-slave-dialog.component.html | 307 ++++++++++++++++++ .../modbus-slave-dialog.component.ts | 198 +++++++++++ .../modbus-values.component.html | 117 +++++++ .../modbus-values/modbus-values.component.ts | 204 ++++++++++++ .../modbus/public-api.ts | 7 + .../opc-server-config.component.html} | 0 .../opc-server-config.component.scss} | 0 .../opc-server-config.component.ts} | 12 +- .../opc-ua-basic-config.component.html | 2 +- .../opc-ua-basic-config.component.ts | 4 +- .../connectors-configuration/public-api.ts | 3 +- .../gateway/gateway-connectors.component.html | 4 + .../gateway/gateway-connectors.component.ts | 4 +- .../lib/gateway/gateway-widget.models.ts | 243 ++++++++++++++ .../widget/widget-components.module.ts | 42 +-- .../gateway-port-tooltip.pipe.ts | 42 +++ .../src/app/modules/home/pipes/public-api.ts | 1 + .../datasource/datasource.abstract.ts | 32 ++ ui-ngx/src/app/shared/abstract/public-api.ts | 1 + .../ellipsis-chip-list.directive.ts | 22 +- .../src/app/shared/directives/public-api.ts | 1 + .../assets/locale/locale.constant-en_US.json | 62 ++++ .../connector-default-configs/modbus.json | 111 +++---- 39 files changed, 2892 insertions(+), 138 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/index.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/public-api.ts rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{server-config/server-config.component.html => opc-server-config/opc-server-config.component.html} (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{server-config/server-config.component.scss => opc-server-config/opc-server-config.component.scss} (100%) rename ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/{server-config/server-config.component.ts => opc-server-config/opc-server-config.component.ts} (89%) create mode 100644 ui-ngx/src/app/modules/home/pipes/gateway-port-tooltip/gateway-port-tooltip.pipe.ts create mode 100644 ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts create mode 100644 ui-ngx/src/app/shared/abstract/public-api.ts rename ui-ngx/src/app/{modules/home/components/widget/lib/gateway/connectors-configuration => shared/directives/ellipsis-chip-list}/ellipsis-chip-list.directive.ts (89%) create mode 100644 ui-ngx/src/app/shared/directives/public-api.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html index f70ed366e1..b5ef346300 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component.html @@ -42,7 +42,7 @@ + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts new file mode 100644 index 0000000000..d61d63edb8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts @@ -0,0 +1,130 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, +} from '@angular/forms'; +import { + ConnectorBaseConfig, + ConnectorType, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { + ModbusMasterTableComponent, + ModbusSlaveConfigComponent, +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus'; +import { EllipsisChipListDirective } from '@shared/directives/public-api'; + +@Component({ + selector: 'tb-modbus-basic-config', + templateUrl: './modbus-basic-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModbusBasicConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ModbusBasicConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusSlaveConfigComponent, + ModbusMasterTableComponent, + EllipsisChipListDirective, + ], + styles: [` + :host { + height: 100%; + } + :host ::ng-deep { + .mat-mdc-tab-group, .mat-mdc-tab-body-wrapper { + height: 100%; + } + } + `] +}) + +export class ModbusBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { + + @Input() generalTabContent: TemplateRef; + + basicFormGroup: FormGroup; + + onChange: (value: string) => void; + onTouched: () => void; + + protected readonly connectorType = ConnectorType; + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder) { + this.basicFormGroup = this.fb.group({ + master: [], + slave: [], + }); + + this.basicFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => { + this.onChange(value); + this.onTouched(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + writeValue(basicConfig: ConnectorBaseConfig): void { + const editedBase = { + slave: basicConfig.slave || {}, + master: basicConfig.master || {}, + }; + + this.basicFormGroup.setValue(editedBase, {emitEvent: false}); + } + + validate(): ValidationErrors | null { + return this.basicFormGroup.valid ? null : { + basicFormGroup: {valid: false} + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html new file mode 100644 index 0000000000..08ef83eeb5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html @@ -0,0 +1,147 @@ + +
+
+
{{ panelTitle | translate }}{{' (' + keysListFormArray.controls.length + ')'}}
+
+
+
+ + + + +
+ {{ keyControl.get('tag').value }} +
+
+
+ +
+
+ gateway.key +
+
+ + + + warning + + +
+
+
+
+ gateway.type +
+
+ + + {{ type }} + + +
+
+
+
gateway.function-code
+
+ + + {{ ModbusFunctionCodeTranslationsMap.get(code) | translate }} + + +
+
+
+
gateway.objects-count
+
+ + + +
+
+
+
gateway.address
+
+ + + +
+
+
+
gateway.value
+
+ + + +
+
+
+
+
+
+ +
+
+
+ +
+
+ +
+ {{ noKeysText }} +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss new file mode 100644 index 0000000000..0e9f9a432f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.scss @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + .tb-modbus-keys-panel { + width: 77vw; + max-width: 700px; + + .title-container { + max-width: 11vw; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap + } + + .key-panel { + height: 500px; + overflow: auto; + } + + .tb-form-panel { + .mat-mdc-icon-button { + width: 56px; + height: 56px; + padding: 16px; + color: rgba(0, 0, 0, 0.54); + } + } + } +} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts new file mode 100644 index 0000000000..a63b2e488e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts @@ -0,0 +1,164 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { AbstractControl, FormGroup, UntypedFormArray, UntypedFormBuilder, Validators } from '@angular/forms'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { + ModbusDataType, + ModbusFunctionCodeTranslationsMap, + ModbusObjectCountByDataType, + ModbusRegisterType, + ModbusValue, + ModbusValueKey, + noLeadTrailSpacesRegex, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { GatewayHelpLinkPipe } from '@home/pipes/public-api'; + +@Component({ + selector: 'tb-modbus-data-keys-panel', + templateUrl: './modbus-data-keys-panel.component.html', + styleUrls: ['./modbus-data-keys-panel.component.scss'], + standalone: true, + imports: [ + CommonModule, + SharedModule, + GatewayHelpLinkPipe, + ] +}) +export class ModbusDataKeysPanelComponent implements OnInit { + + @Input() isMaster = false; + @Input() panelTitle: string; + @Input() addKeyTitle: string; + @Input() deleteKeyTitle: string; + @Input() noKeysText: string; + @Input() register: ModbusRegisterType; + @Input() keysType: ModbusValueKey; + @Input() values: ModbusValue[]; + @Input() popover: TbPopoverComponent; + + @Output() keysDataApplied = new EventEmitter>(); + + keysListFormArray: UntypedFormArray; + errorText = ''; + modbusDataTypes = Object.values(ModbusDataType); + withFunctionCode = true; + functionCodesMap = new Map(); + defaultFunctionCodes = []; + + readonly editableDataTypes = [ModbusDataType.BYTES, ModbusDataType.BITS, ModbusDataType.STRING]; + readonly ModbusFunctionCodeTranslationsMap = ModbusFunctionCodeTranslationsMap; + readonly defaultReadFunctionCodes = [3, 4]; + readonly defaultWriteFunctionCodes = [5, 6, 15, 16]; + + constructor(private fb: UntypedFormBuilder) {} + + ngOnInit(): void { + this.keysListFormArray = this.prepareKeysFormArray(this.values); + this.withFunctionCode = !this.isMaster || (this.keysType !== ModbusValueKey.ATTRIBUTES && this.keysType !== ModbusValueKey.TELEMENTRY); + this.defaultFunctionCodes = this.getDefaultFunctionCodes(); + } + + trackByKey(_: number, keyControl: AbstractControl): AbstractControl { + return keyControl; + } + + addKey(): void { + const dataKeyFormGroup = this.fb.group({ + tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + value: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + type: [ModbusDataType.STRING, [Validators.required]], + address: [0, [Validators.required]], + objectsCount: [1, [Validators.required]], + functionCode: [this.getDefaultFunctionCodes()[0]] + }); + this.observeKeyDataType(dataKeyFormGroup); + + this.keysListFormArray.push(dataKeyFormGroup); + } + + deleteKey($event: Event, index: number): void { + if ($event) { + $event.stopPropagation(); + } + this.keysListFormArray.removeAt(index); + this.keysListFormArray.markAsDirty(); + } + + cancel(): void { + this.popover?.hide(); + } + + applyKeysData(): void { + this.keysDataApplied.emit(this.keysListFormArray.value); + } + + private prepareKeysFormArray(values: ModbusValue[]): UntypedFormArray { + const keysControlGroups: Array = []; + if (values) { + values.forEach(keyData => { + const { tag, value, type, address, objectsCount, functionCode } = keyData; + const dataKeyFormGroup = this.fb.group({ + tag: [tag, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + value: [value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + type: [type, [Validators.required]], + address: [address, [Validators.required]], + objectsCount: [objectsCount, [Validators.required]], + functionCode: [functionCode, []], + }); + this.observeKeyDataType(dataKeyFormGroup); + this.functionCodesMap.set(tag+address, this.getFunctionCodes(type)); + + keysControlGroups.push(dataKeyFormGroup); + }); + } + return this.fb.array(keysControlGroups); + } + + private observeKeyDataType(keyFormGroup: FormGroup): void { + keyFormGroup.get('type').valueChanges.subscribe(dataType => { + const objectsCountControl = keyFormGroup.get('objectsCount'); + if (!this.editableDataTypes.includes(dataType)) { + objectsCountControl.patchValue(ModbusObjectCountByDataType[dataType]); + } + const keyId = keyFormGroup.get('tag').value + keyFormGroup.get('address').value; + this.functionCodesMap.set(keyId, this.getFunctionCodes(dataType)); + }); + } + + private getFunctionCodes(dataType: ModbusDataType): number[] { + if (this.keysType === ModbusValueKey.ATTRIBUTES_UPDATES) { + return this.defaultWriteFunctionCodes; + } + const functionCodes = this.defaultReadFunctionCodes; + if (dataType === ModbusDataType.BITS) { + const bitsFunctionCodes = [1, 2]; + bitsFunctionCodes.forEach(code => functionCodes.push(code)); + functionCodes.sort(); + } + if (this.keysType === ModbusValueKey.RPC_REQUESTS) { + this.defaultWriteFunctionCodes.forEach(code => functionCodes.push(code)); + } + return functionCodes; + } + + private getDefaultFunctionCodes(): number[] { + return this.keysType === ModbusValueKey.ATTRIBUTES_UPDATES ? this.defaultWriteFunctionCodes : this.defaultReadFunctionCodes; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html new file mode 100644 index 0000000000..ac5e6f3108 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.html @@ -0,0 +1,131 @@ + +
+
+ +
+
+ {{ 'gateway.servers-slaves' | translate}} +
+ + + +
+
+ +
+ + +   + + + +
+
+
+ + + + {{ 'gateway.name' | translate }} + + + {{ mapping['name'] }} + + + + + {{ 'gateway.client-communication-type' | translate }} + + + {{ ModbusClientTypeLabelsMap.get(mapping['type']) }} + + + + + + +
+ + +
+
+ + + + + +
+
+
+ + +
+
+ +
+
+ + widget.no-data-found + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.scss new file mode 100644 index 0000000000..82cead07a4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.scss @@ -0,0 +1,101 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../scss/constants'; + +:host { + width: 100%; + height: 100%; + display: block; + .tb-master-table { + .tb-master-table-content { + width: 100%; + height: 100%; + background: #fff; + overflow: hidden; + + &.tb-outlined-border { + 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; + } + + .mat-toolbar-tools{ + min-height: auto; + } + + .title-container{ + overflow: hidden; + } + + .tb-master-table-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .table-container { + overflow: auto; + .mat-mdc-table { + table-layout: fixed; + min-width: 450px; + + .table-value-column { + padding: 0 12px; + width: 23%; + + &.request-column { + width: 38%; + } + } + } + } + + .ellipsis { + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + .no-data-found { + height: calc(100% - 120px); + } + + @media #{$mat-xs} { + .mat-toolbar { + height: auto; + min-height: 100px; + + .tb-master-table-title{ + padding-bottom: 5px; + width: 100%; + } + } + } +} + +:host ::ng-deep { + mat-cell.tb-value-cell { + cursor: pointer; + .mat-icon { + height: 24px; + width: 24px; + font-size: 24px; + color: #757575 + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts new file mode 100644 index 0000000000..b1f2c09689 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts @@ -0,0 +1,231 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + forwardRef, + OnDestroy, + OnInit, + ViewChild, +} from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { Subject } from 'rxjs'; +import { debounceTime, distinctUntilChanged, take, takeUntil } from 'rxjs/operators'; +import { + ControlValueAccessor, + FormArray, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + ValidationErrors, + Validator, +} from '@angular/forms'; +import { + ModbusClientTypeLabelsMap, ModbusMasterConfig, SlaveConfig, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { TbDatasource } from '@shared/abstract/datasource/datasource.abstract'; +import { ModbusSlaveDialogComponent } from '@home/components/widget/lib/gateway/connectors-configuration/modbus'; + +@Component({ + selector: 'tb-modbus-master-table', + templateUrl: './modbus-master-table.component.html', + styleUrls: ['./modbus-master-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModbusMasterTableComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ModbusMasterTableComponent), + multi: true + } + ], + standalone: true, + imports: [CommonModule, SharedModule] +}) +export class ModbusMasterTableComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { + + @ViewChild('searchInput') searchInputField: ElementRef; + + textSearchMode = false; + dataSource: SlavesDatasource; + hidePageSize = false; + activeValue = false; + dirtyValue = false; + masterFormGroup: UntypedFormGroup; + textSearch = this.fb.control('', {nonNullable: true}); + + readonly ModbusClientTypeLabelsMap = ModbusClientTypeLabelsMap; + + private onChange: (value: string) => void = () => {}; + private onTouched: () => void = () => {}; + + private destroy$ = new Subject(); + + constructor( + public translate: TranslateService, + public dialog: MatDialog, + private dialogService: DialogService, + private fb: FormBuilder + ) { + this.masterFormGroup = this.fb.group({ slaves: this.fb.array([])}); + this.dirtyValue = !this.activeValue; + this.dataSource = new SlavesDatasource(); + } + + get slaves(): FormArray { + return this.masterFormGroup.get('slaves') as FormArray; + } + + ngOnInit(): void { + this.masterFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.updateTableData(value.slaves); + this.onChange(value); + this.onTouched(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + ngAfterViewInit(): void { + this.textSearch.valueChanges.pipe( + debounceTime(150), + distinctUntilChanged((prev, current) => (prev ?? '') === current.trim()), + takeUntil(this.destroy$) + ).subscribe((text) => { + const searchText = text.trim(); + this.updateTableData(this.slaves.value, searchText.trim()); + }); + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + writeValue(master: ModbusMasterConfig): void { + this.slaves.clear(); + this.pushDataAsFormArrays(master.slaves); + } + + validate(): ValidationErrors | null { + return this.slaves.controls.length ? null : { + slavesFormGroup: {valid: false} + }; + } + + enterFilterMode(): void { + this.textSearchMode = true; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode(): void { + this.updateTableData(this.slaves.value); + this.textSearchMode = false; + this.textSearch.reset(); + } + + manageSlave($event: Event, index?: number): void { + if ($event) { + $event.stopPropagation(); + } + const value = isDefinedAndNotNull(index) ? this.slaves.at(index).value : {}; + this.dialog.open(ModbusSlaveDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + value, + buttonTitle: isUndefinedOrNull(index) ? 'action.add' : 'action.apply' + } + }).afterClosed() + .pipe(take(1), takeUntil(this.destroy$)) + .subscribe(res => { + if (res) { + if (isDefinedAndNotNull(index)) { + this.slaves.at(index).patchValue(res); + } else { + this.slaves.push(this.fb.control(res)); + } + this.masterFormGroup.markAsDirty(); + } + }); + } + + deleteMapping($event: Event, index: number): void { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('gateway.delete-slave-title'), + '', + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((result) => { + if (result) { + this.slaves.removeAt(index); + this.masterFormGroup.markAsDirty(); + } + }); + } + + private updateTableData(data: SlaveConfig[], textSearch?: string): void { + let tableValue = data; + if (textSearch) { + tableValue = tableValue.filter(value => + Object.values(value).some(val => + val.toString().toLowerCase().includes(textSearch.toLowerCase()) + ) + ); + } + this.dataSource.loadData(tableValue); + } + + private pushDataAsFormArrays(slaves: SlaveConfig[]): void { + if (slaves?.length) { + slaves.forEach((slave: SlaveConfig) => this.slaves.push(this.fb.control(slave))); + } + } +} + +export class SlavesDatasource extends TbDatasource { + constructor() { + super(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html new file mode 100644 index 0000000000..900973d327 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html @@ -0,0 +1,44 @@ +
+
+
gateway.client-cert-path
+
+ + + +
+
+
+
gateway.private-key-path
+
+ + + +
+
+
+
gateway.password
+
+ + +
+ +
+
+
+
+
+
gateway.server-hostname
+
+ + + +
+
+
+ + + {{ 'gateway.request-client-certificate' | translate }} + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts new file mode 100644 index 0000000000..2f5786882b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts @@ -0,0 +1,126 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectionStrategy, Component, forwardRef, Input, OnChanges, OnDestroy } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { + ModbusSecurity, + noLeadTrailSpacesRegex, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { SecurityConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-modbus-security-config', + templateUrl: './modbus-security-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModbusSecurityConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ModbusSecurityConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + SecurityConfigComponent, + ] +}) +export class ModbusSecurityConfigComponent implements ControlValueAccessor, Validator, OnChanges, OnDestroy { + + @Input() isMaster = false; + + securityConfigFormGroup: UntypedFormGroup; + + private onChange: (value: ModbusSecurity) => void; + private onTouched: () => void; + + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder) { + this.securityConfigFormGroup = this.fb.group({ + certfile: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + keyfile: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + password: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + server_hostname: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + }); + + this.observeValueChanges(); + } + + ngOnChanges(): void { + if (this.isMaster) { + this.securityConfigFormGroup = this.fb.group({ + certfile: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + keyfile: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + password: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + reqclicert: [false, []], + }); + this.observeValueChanges(); + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: (value: ModbusSecurity) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + validate(): ValidationErrors | null { + return this.securityConfigFormGroup.valid ? null : { + serverConfigFormGroup: { valid: false } + }; + } + + writeValue(securityConfig: ModbusSecurity): void { + this.securityConfigFormGroup.patchValue(securityConfig, {emitEvent: false}); + } + + private observeValueChanges(): void { + this.securityConfigFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value: ModbusSecurity) => { + this.onChange(value); + this.onTouched(); + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html new file mode 100644 index 0000000000..43c71958bf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html @@ -0,0 +1,217 @@ + +
+
+
gateway.server-slave-config
+ + {{ ModbusProtocolLabelsMap.get(type) }} + +
+
+
+
gateway.host
+
+ + + + warning + + +
+
+
+
gateway.port
+
+ + + + warning + + +
+
+ +
+
gateway.port
+
+ + + +
+
+
+
+
+ gateway.method +
+
+ + + {{ ModbusMethodLabelsMap.get(method) }} + + +
+
+
+
+
gateway.unit-id
+
+ + + +
+
+
+
gateway.device-name
+
+ + + +
+
+
+
gateway.device-profile
+
+ + + +
+
+
+
gateway.poll-period
+
+ + + +
+
+
+
gateway.baudrate
+
+ + + +
+
+
+ + + {{ 'gateway.send-data-TB' | translate }} + + +
+ + + +
gateway.advanced-connection-settings
+
+
+
+
+
gateway.byte-order
+
+ + + {{ order }} + + +
+
+ + + + + + {{ 'gateway.tls-connection' | translate }} + + + + + + + +
+
gateway.vendor-name
+
+ + + +
+
+
+
gateway.product-code
+
+ + + +
+
+
+
gateway.vendor-url
+
+ + + +
+
+
+
gateway.product-name
+
+ + + +
+
+
+
gateway.model-name
+
+ + + +
+
+
+
+
+
+
gateway.values
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts new file mode 100644 index 0000000000..3bb21cd9d4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts @@ -0,0 +1,185 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectionStrategy, Component, forwardRef, OnDestroy } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormControl, + UntypedFormGroup, + ValidationErrors, + Validator, Validators, +} from '@angular/forms'; +import { + ModbusMethodLabelsMap, + ModbusMethodType, + ModbusOrderType, + ModbusProtocolLabelsMap, + ModbusProtocolType, + noLeadTrailSpacesRegex, + PortLimits, SlaveConfig, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { + ModbusValuesComponent, + ModbusSecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus'; +import { GatewayPortTooltipPipe } from '@home/pipes/public-api'; + +@Component({ + selector: 'tb-modbus-server-config', + templateUrl: './modbus-slave-config.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModbusSlaveConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ModbusSlaveConfigComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusValuesComponent, + ModbusSecurityConfigComponent, + GatewayPortTooltipPipe, + ], + styles: [` + :host { + .nested-expansion-header { + .mat-content { + height: 100%; + } + } + } + `], +}) +export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validator, OnDestroy { + + slaveConfigFormGroup: UntypedFormGroup; + showSecurityControl: UntypedFormControl; + ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; + ModbusMethodLabelsMap = ModbusMethodLabelsMap; + portLimits = PortLimits; + + readonly modbusProtocolTypes = Object.values(ModbusProtocolType); + readonly modbusMethodTypes = Object.values(ModbusMethodType); + readonly modbusOrderType = Object.values(ModbusOrderType); + readonly ModbusProtocolType = ModbusProtocolType; + readonly serialSpecificControlKeys = ['serialPort', 'baudrate']; + readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host']; + + private onChange: (value: SlaveConfig) => void; + private onTouched: () => void; + + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder) { + this.showSecurityControl = this.fb.control(false); + this.slaveConfigFormGroup = this.fb.group({ + type: [ModbusProtocolType.TCP, []], + host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], + serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + method: [ModbusMethodType.SOCKET, []], + unitId: [null, []], + baudrate: [null, []], + deviceName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + deviceType: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + pollPeriod: [null, []], + sendDataToThingsBoard: [false, []], + byteOrder:[ModbusOrderType.BIG, []], + security: [], + identity: this.fb.group({ + vendorName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + productCode: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + vendorUrl: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + productName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + modelName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + }), + values: [], + }); + + this.slaveConfigFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value: SlaveConfig) => { + if (value.type === ModbusProtocolType.Serial) { + value.port = value.serialPort; + delete value.serialPort; + } + this.onChange(value); + this.onTouched(); + }); + + this.observeTypeChange(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: (value: SlaveConfig) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + validate(): ValidationErrors | null { + return this.slaveConfigFormGroup.valid ? null : { + serverConfigFormGroup: { valid: false } + }; + } + + writeValue(slaveConfig: SlaveConfig): void { + if (slaveConfig.type === ModbusProtocolType.Serial) { + slaveConfig.serialPort = slaveConfig.port as string; + delete slaveConfig.port; + } + this.slaveConfigFormGroup.patchValue(slaveConfig, {emitEvent: false}); + this.showSecurityControl.patchValue(!!slaveConfig.security); + this.updateControlsEnabling(slaveConfig.type); + } + + private observeTypeChange(): void { + this.slaveConfigFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(type => { + this.updateControlsEnabling(type); + }); + } + + private updateControlsEnabling(type: ModbusProtocolType): void { + if (type === ModbusProtocolType.Serial) { + this.serialSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({emitEvent: false})); + this.tcpUdpSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({emitEvent: false})); + } else { + this.serialSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({emitEvent: false})); + this.tcpUdpSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({emitEvent: false})); + } + }; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html new file mode 100644 index 0000000000..5c8fb4c397 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html @@ -0,0 +1,307 @@ + +
+ +

{{ 'gateway.server-slave' | translate }}

+ +
+ +
+
+
+
gateway.name
+
+ + + +
+
+
+
+
+
gateway.server-connection
+ + {{ ModbusProtocolLabelsMap.get(type) }} + +
+
+
+
gateway.host
+
+ + + + warning + + +
+
+
+
gateway.port
+
+ + + + warning + + +
+
+ +
+
gateway.port
+
+ + + +
+
+
+
+
+ gateway.method +
+
+ + + {{ ModbusMethodLabelsMap.get(method) }} + + +
+
+
+ +
+
gateway.baudrate
+
+ + + +
+
+
+
gateway.bytesize
+
+ + + +
+
+
+
gateway.stopbits
+
+ + + +
+
+
+
gateway.parity
+
+ + + +
+
+
+ + + {{ 'gateway.strict' | translate }} + + +
+
+
+
gateway.unit-id
+
+ + + +
+
+
+
gateway.device-name
+
+ + + +
+
+
+
gateway.device-profile
+
+ + + +
+
+
+ + + {{ 'gateway.send-data-on-change' | translate }} + + +
+ + + +
gateway.advanced-connection-settings
+
+
+
+
+
gateway.connection-timeout
+
+ + + +
+
+
+
gateway.byte-order
+
+ + + {{ order }} + + +
+
+
+
gateway.word-order
+
+ + + {{ order }} + + +
+
+ + + + + + {{ 'gateway.tls-connection' | translate }} + + + + + + +
+ + + {{ 'gateway.retries' | translate }} + + +
+
+ + + {{ 'gateway.retries-on-empty' | translate }} + + +
+
+ + + {{ 'gateway.retries-on-invalid' | translate }} + + +
+
+
gateway.poll-period
+
+ + + +
+
+
+
gateway.connect-attempt-time
+
+ + + +
+
+
+
gateway.connect-attempt-count
+
+ + + +
+
+
+
gateway.wait-after-failed-attempts
+
+ + + +
+
+
+
+
+
gateway.values
+ +
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts new file mode 100644 index 0000000000..202ef260c7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts @@ -0,0 +1,198 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectionStrategy, Component, forwardRef, Inject, OnDestroy } from '@angular/core'; +import { + FormBuilder, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormControl, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { + MappingInfo, + ModbusMethodLabelsMap, + ModbusMethodType, + ModbusOrderType, + ModbusProtocolLabelsMap, + ModbusProtocolType, + noLeadTrailSpacesRegex, + PortLimits, SlaveConfig, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { Subject } from 'rxjs'; +import { + ModbusValuesComponent, + ModbusSecurityConfigComponent, +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { GatewayPortTooltipPipe } from '@home/pipes/gateway-port-tooltip/gateway-port-tooltip.pipe'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-modbus-slave-dialog', + templateUrl: './modbus-slave-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModbusSlaveDialogComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ModbusSlaveDialogComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusValuesComponent, + ModbusSecurityConfigComponent, + GatewayPortTooltipPipe, + ], + styles: [` + :host { + .slaves-config-container { + width: 900px; + } + .nested-expansion-header { + .mat-content { + height: 100%; + overflow: hidden; + } + } + } + `], +}) +export class ModbusSlaveDialogComponent extends DialogComponent implements OnDestroy { + + slaveConfigFormGroup: UntypedFormGroup; + showSecurityControl: UntypedFormControl; + portLimits = PortLimits; + + readonly modbusProtocolTypes = Object.values(ModbusProtocolType); + readonly modbusMethodTypes = Object.values(ModbusMethodType); + readonly modbusOrderType = Object.values(ModbusOrderType); + readonly ModbusProtocolType = ModbusProtocolType; + readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; + readonly ModbusMethodLabelsMap = ModbusMethodLabelsMap; + readonly modbusHelpLink = + 'https://thingsboard.io/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters'; + readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict']; + readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host']; + + private destroy$ = new Subject(); + + constructor( + private fb: FormBuilder, + protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: MappingInfo, + public dialogRef: MatDialogRef, + ) { + super(store, router, dialogRef); + + this.showSecurityControl = this.fb.control(false); + this.slaveConfigFormGroup = this.fb.group({ + name: ['', [Validators.required]], + type: [ModbusProtocolType.TCP, [Validators.required]], + host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], + serialPort: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + method: [ModbusMethodType.SOCKET, []], + baudrate: [null, []], + stopbits: [null, []], + bytesize: [null, []], + parity: [null, []], + strict: [false, []], + unitId: [null, []], + deviceName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + deviceType: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + sendDataOnlyOnChange: [false, []], + timeout: [], + byteOrder: [ModbusOrderType.BIG, []], + wordOrder: [ModbusOrderType.BIG, []], + retries: [false, []], + retryOnEmpty: [false, []], + retryOnInvalid: [false, []], + pollPeriod: [null, []], + connectAttemptTimeMs: [null, []], + connectAttemptCount: [null, []], + waitAfterFailedAttemptsMs: [null, []], + values: [{}, []], + security: [], + }); + + this.slaveConfigFormGroup.patchValue({ + ...this.data.value, + values: { + attributes: this.data.value.attributes ?? [], + timeseries: this.data.value.timeseries ?? [], + attributeUpdates: this.data.value.attributeUpdates ?? [], + rpc: this.data.value.rpc ?? [], + } + }); + this.showSecurityControl.patchValue(!!this.data.value.security); + this.updateControlsEnabling(this.data.value.type); + this.observeTypeChange(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + if (this.slaveConfigFormGroup.valid) { + const slaveResult = {...this.slaveConfigFormGroup.value, ...this.slaveConfigFormGroup.value.values}; + delete slaveResult.values; + if (slaveResult.type === ModbusProtocolType.Serial) { + slaveResult.port = slaveResult.serialPort; + delete slaveResult.serialPort; + } + this.dialogRef.close(slaveResult); + } + } + + private observeTypeChange(): void { + this.slaveConfigFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(type => { + this.updateControlsEnabling(type); + }); + } + + private updateControlsEnabling(type: ModbusProtocolType): void { + if (type === ModbusProtocolType.Serial) { + this.serialSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({emitEvent: false})); + this.tcpUdpSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({emitEvent: false})); + } else { + this.serialSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({emitEvent: false})); + this.tcpUdpSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({emitEvent: false})); + } + }; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html new file mode 100644 index 0000000000..5b8077d77c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.html @@ -0,0 +1,117 @@ + + + +
+ +
+
+ + + + +
+ +
+
+
+
+ + +
+
gateway.attributes
+
+ + + {{ attribute.tag }} + + + + + + +
+
+
+
gateway.telementry
+
+ + + {{ telemetry.tag }} + + + + + + +
+
+
+
gateway.attribute-updates
+
+ + + {{ attributeUpdate.tag }} + + + + + + +
+
+
+
gateway.rpc-requests
+
+ + + {{ rpcRequest.tag }} + + + + + + +
+
+
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts new file mode 100644 index 0000000000..76b376fb1b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts @@ -0,0 +1,204 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + forwardRef, + Input, + OnChanges, + OnDestroy, + Renderer2, + ViewContainerRef +} from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, +} from '@angular/forms'; +import { + ModbusKeysAddKeyTranslationsMap, + ModbusKeysDeleteKeyTranslationsMap, + ModbusKeysNoKeysTextTranslationsMap, + ModbusKeysPanelTitleTranslationsMap, + ModbusRegisterTranslationsMap, + ModbusRegisterType, + ModbusRegisterValues, + ModbusValueKey, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { ModbusDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { EllipsisChipListDirective } from '@shared/directives/public-api'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; + +@Component({ + selector: 'tb-modbus-values', + templateUrl: './modbus-values.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ModbusValuesComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ModbusValuesComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + EllipsisChipListDirective, + ], + styles: [` + :host { + .mat-mdc-tab-body-wrapper { + min-height: 320px; + } + } + `] +}) + +export class ModbusValuesComponent implements ControlValueAccessor, Validator, OnChanges, OnDestroy { + + @Input() singleMode = false; + + modbusRegisterTypes: ModbusRegisterType[] = Object.values(ModbusRegisterType); + modbusValueKeys = Object.values(ModbusValueKey); + ModbusValuesTranslationsMap = ModbusRegisterTranslationsMap; + ModbusValueKey = ModbusValueKey; + valuesFormGroup: FormGroup; + + private onChange: (value: string) => void; + private onTouched: () => void; + + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private cdr: ChangeDetectorRef, + ) { + this.valuesFormGroup = this.fb.group(this.modbusRegisterTypes.reduce((registersAcc, register) => { + return { + ...registersAcc, + [register]: this.fb.group(this.modbusValueKeys.reduce((acc, key) => ({...acc, [key]: [[], []]}), {})), + }; + }, {})); + + this.observeValuesChanges(); + } + + ngOnChanges(): void { + if (this.singleMode) { + this.valuesFormGroup = this.fb.group(this.modbusValueKeys.reduce((acc, key) => ({...acc, [key]: [[], []]}), {})); + this.observeValuesChanges(); + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + writeValue(values: ModbusRegisterValues): void { + this.valuesFormGroup.patchValue(values, {emitEvent: false}); + } + + validate(): ValidationErrors | null { + return this.valuesFormGroup.valid ? null : { + valuesFormGroup: {valid: false} + }; + } + + getValueGroup(valueKey: ModbusValueKey, register?: ModbusRegisterType) { + return register ? this.valuesFormGroup.get(register).get(valueKey).value : this.valuesFormGroup.get(valueKey).value; + } + + manageKeys($event: Event, matButton: MatButton, keysType: ModbusValueKey, register?: ModbusRegisterType): void { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const group = this.valuesFormGroup; + + const keysControl = register ? group.get(register).get(keysType) : group.get(keysType); + const ctx = { + values: keysControl.value, + isMaster: !this.singleMode, + keysType, + panelTitle: ModbusKeysPanelTitleTranslationsMap.get(keysType), + addKeyTitle: ModbusKeysAddKeyTranslationsMap.get(keysType), + deleteKeyTitle: ModbusKeysDeleteKeyTranslationsMap.get(keysType), + noKeysText: ModbusKeysNoKeysTextTranslationsMap.get(keysType) + }; + const dataKeysPanelPopover = this.popoverService.displayPopover( + trigger, + this.renderer, + this.viewContainerRef, + ModbusDataKeysPanelComponent, + 'leftBottom', + false, + null, + ctx, + {}, + {}, + {}, + true + ); + dataKeysPanelPopover.tbComponentRef.instance.popover = dataKeysPanelPopover; + dataKeysPanelPopover.tbComponentRef.instance.keysDataApplied.pipe(takeUntil(this.destroy$)).subscribe((keysData) => { + dataKeysPanelPopover.hide(); + keysControl.patchValue(keysData); + keysControl.markAsDirty(); + this.cdr.markForCheck(); + }); + } + } + + private observeValuesChanges(): void { + this.valuesFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => { + this.onChange(value); + this.onTouched(); + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/public-api.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/public-api.ts new file mode 100644 index 0000000000..803a8ff8dd --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/public-api.ts @@ -0,0 +1,7 @@ +export * from './modbus-basic-config/modbus-basic-config.component'; +export * from './modbus-values/modbus-values.component'; +export * from './modbus-data-keys-panel/modbus-data-keys-panel.component'; +export * from './modbus-slave-config/modbus-slave-config.component'; +export * from './modbus-master-table/modbus-master-table.component'; +export * from './modbus-slave-dialog/modbus-slave-dialog.component'; +export * from './modbus-security-config/modbus-security-config.component'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.scss diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts similarity index 89% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts index 98a36182a6..955e7e8145 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/server-config/server-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts @@ -38,19 +38,19 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @Component({ - selector: 'tb-server-config', - templateUrl: './server-config.component.html', - styleUrls: ['./server-config.component.scss'], + selector: 'tb-opc-server-config', + templateUrl: './opc-server-config.component.html', + styleUrls: ['./opc-server-config.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, providers: [ { provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ServerConfigComponent), + useExisting: forwardRef(() => OpcServerConfigComponent), multi: true }, { provide: NG_VALIDATORS, - useExisting: forwardRef(() => ServerConfigComponent), + useExisting: forwardRef(() => OpcServerConfigComponent), multi: true } ], @@ -61,7 +61,7 @@ import { takeUntil } from 'rxjs/operators'; SecurityConfigComponent, ] }) -export class ServerConfigComponent implements ControlValueAccessor, Validator, OnDestroy { +export class OpcServerConfigComponent implements ControlValueAccessor, Validator, OnDestroy { securityPolicyTypes = SecurityPolicyTypes; serverConfigFormGroup: UntypedFormGroup; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html index 1089fd114c..5f8b25af9d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.html @@ -20,7 +20,7 @@ - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts index 2c1dd7dd2b..67aa4d9bd3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-ua-basic-config/opc-ua-basic-config.component.ts @@ -35,7 +35,7 @@ import { BrokerConfigControlComponent, MappingTableComponent, SecurityConfigComponent, - ServerConfigComponent, + OpcServerConfigComponent, WorkersConfigControlComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; import { takeUntil } from 'rxjs/operators'; @@ -65,7 +65,7 @@ import { Subject } from 'rxjs'; WorkersConfigControlComponent, BrokerConfigControlComponent, MappingTableComponent, - ServerConfigComponent, + OpcServerConfigComponent, ], styleUrls: ['./opc-ua-basic-config.component.scss'] }) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts index 5e185ddf0a..f98e6c569e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/public-api.ts @@ -17,10 +17,11 @@ export * from './mapping-table/mapping-table.component'; export * from './device-info-table/device-info-table.component'; export * from './security-config/security-config.component'; -export * from './server-config/server-config.component'; +export * from './opc-server-config/opc-server-config.component'; export * from './mapping-data-keys-panel/mapping-data-keys-panel.component'; export * from './type-value-panel/type-value-panel.component'; export * from './broker-config-control/broker-config-control.component'; export * from './workers-config-control/workers-config-control.component'; export * from './opc-ua-basic-config/opc-ua-basic-config.component'; export * from './mqtt-basic-config/mqtt-basic-config.component'; +export * from './modbus/public-api'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 7896ac2516..5fc5693bec 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -182,6 +182,10 @@ formControlName="basicConfig" [generalTabContent]="generalTabContent"> + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 14a8c15c00..a3ededb498 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -92,7 +92,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie allowBasicConfig = new Set([ ConnectorType.MQTT, - ConnectorType.OPCUA + ConnectorType.OPCUA, + ConnectorType.MODBUS, ]); gatewayLogLevel = Object.values(GatewayLogLevel); @@ -732,6 +733,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie switch (connector.type) { case ConnectorType.MQTT: case ConnectorType.OPCUA: + case ConnectorType.MODBUS: this.connectorForm.get('type').patchValue(connector.type, {emitValue: false, onlySelf: true}); setTimeout(() => { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 9805008503..3e6471c0b1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -181,6 +181,8 @@ export interface ConnectorBaseConfig { server?: ServerConfig; broker?: BrokerConfig; workers?: WorkersConfig; + master?: ModbusMasterConfig; + slave?: ModbusSlave; } export interface WorkersConfig { @@ -739,3 +741,244 @@ export const SecurityPolicyTypes = [ { value: SecurityPolicy.BASIC256, name: 'Basic256' }, { value: SecurityPolicy.BASIC256SHA, name: 'Basic256SHA256' } ]; + +export enum ModbusProtocolType { + TCP = 'tcp', + UDP = 'udp', + Serial = 'serial', +} + +export const ModbusProtocolLabelsMap = new Map( + [ + [ModbusProtocolType.TCP, 'TCP'], + [ModbusProtocolType.UDP, 'UDP'], + [ModbusProtocolType.Serial, 'Serial'], + ] +); + +export enum ModbusMethodType { + SOCKET = 'socket', + RTU = 'rtu', +} + +export const ModbusMethodLabelsMap = new Map( + [ + [ModbusMethodType.SOCKET, 'Socket'], + [ModbusMethodType.RTU, 'RTU'], + ] +); + +export enum ModbusOrderType { + BIG = 'BIG', + LITTLE = 'LITTLE', +} + +export enum ModbusRegisterType { + HoldingRegister = 'holding_registers', + CoilsInitializer = 'coils_initializer', + InputRegister = 'input_registers', + DiscreteInputs = 'discrete_inputs' +} + +export const ModbusRegisterTranslationsMap = new Map( + [ + [ModbusRegisterType.HoldingRegister, 'gateway.holding_registers'], + [ModbusRegisterType.CoilsInitializer, 'gateway.coils_initializer'], + [ModbusRegisterType.InputRegister, 'gateway.input_registers'], + [ModbusRegisterType.DiscreteInputs, 'gateway.discrete_inputs'] + ] +); + +export enum ModbusDataType { + STRING = 'string', + BYTES = 'bytes', + BITS = 'bits', + INT16 = '16int', + UINT16 = '16uint', + FLOAT16 = '16float', + INT32 = '32int', + UINT32 = '32uint', + FLOAT32 = '32float', + INT64 = '64int', + UINT64 = '64uint', + FLOAT64 = '64float' +} + +export enum ModbusObjectCountByDataType { + '16int' = 1, + '16uint' = 1, + '16float' = 1, + '32int' = 2, + '32uint' = 2, + '32float' = 2, + '64int' = 4, + '64uint' = 4, + '64float' = 4, +} + +export enum ModbusValueField { + Tag = 'tag', + Type = 'type', + ObjectsCount = 'objectsCount', + Address = 'address', + Value = 'value', +} + +export const ModbusFieldsTranslationsMap = new Map( + [ + [ModbusValueField.Tag, 'gateway.tag'], + [ModbusValueField.Type, 'gateway.type'], + [ModbusValueField.ObjectsCount, 'gateway.objects_count'], + [ModbusValueField.Address, 'gateway.address'], + [ModbusValueField.Value, 'gateway.value'] + ] +); + +export enum ModbusValueKey { + ATTRIBUTES = 'attributes', + TELEMENTRY = 'timeseries', + ATTRIBUTES_UPDATES = 'attributeUpdates', + RPC_REQUESTS = 'rpc', +} + +export const ModbusKeysPanelTitleTranslationsMap = new Map( + [ + [ModbusValueKey.ATTRIBUTES, 'gateway.attributes'], + [ModbusValueKey.TELEMENTRY, 'gateway.telementry'], + [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.attribute-updates'], + [ModbusValueKey.RPC_REQUESTS, 'gateway.rpc-requests'] + ] +); + +export const ModbusKeysAddKeyTranslationsMap = new Map( + [ + [ModbusValueKey.ATTRIBUTES, 'gateway.add-attribute'], + [ModbusValueKey.TELEMENTRY, 'gateway.add-telementry'], + [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.add-attribute-update'], + [ModbusValueKey.RPC_REQUESTS, 'gateway.add-rpc-request'] + ] +); + +export const ModbusKeysDeleteKeyTranslationsMap = new Map( + [ + [ModbusValueKey.ATTRIBUTES, 'gateway.delete-attribute'], + [ModbusValueKey.TELEMENTRY, 'gateway.delete-telementry'], + [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.delete-attribute-update'], + [ModbusValueKey.RPC_REQUESTS, 'gateway.delete-rpc-requests'] + ] +); + +export const ModbusKeysNoKeysTextTranslationsMap = new Map( + [ + [ModbusValueKey.ATTRIBUTES, 'gateway.no-attributes'], + [ModbusValueKey.TELEMENTRY, 'gateway.no-telementry'], + [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.no-attribute-updates'], + [ModbusValueKey.RPC_REQUESTS, 'gateway.no-rpc-requests'] + ] +); + +export const ModbusClientTypeLabelsMap = new Map( + [ + [ModbusProtocolType.TCP, 'TCP/UDP'], + [ModbusProtocolType.UDP, 'TCP/UDP'], + [ModbusProtocolType.Serial, 'Serial'], + ] +); + +export const ModbusFunctionCodeTranslationsMap = new Map( + [ + [1, 'gateway.read-coils'], + [2, 'gateway.read-discrete-inputs'], + [3, 'gateway.read-multiple-holding-registers'], + [4, 'gateway.read-input-registers'], + [5, 'gateway.write-coil'], + [6, 'gateway.write-register'], + [15, 'gateway.write-coils'], + [16, 'gateway.write-registers'], + ] +); + +export interface ModbusMasterConfig { + slaves: SlaveConfig[]; +} + +export interface SlaveConfig { + name: string; + host?: string; + port: string | number; + serialPort?: string; + type: ModbusProtocolType; + method: ModbusMethodType; + timeout: number; + byteOrder: ModbusOrderType; + wordOrder: ModbusOrderType; + retries: boolean; + retryOnEmpty: boolean; + retryOnInvalid: boolean; + pollPeriod: number; + unitId: number; + deviceName: string; + deviceType?: string; + sendDataOnlyOnChange: boolean; + connectAttemptTimeMs: number; + connectAttemptCount: number; + waitAfterFailedAttemptsMs: number; + attributes: ModbusValue[]; + timeseries: ModbusValue[]; + attributeUpdates: ModbusValue[]; + rpc: ModbusValue[]; + security?: ModbusSecurity; + baudrate?: number; + stopbits?: number; + bytesize?: number; + parity?: string; + strict?: boolean; +} + +export interface ModbusValue { + tag: string; + type: ModbusDataType; + functionCode?: number; + objectsCount: number; + address: number; + value?: string; +} + +export interface ModbusSecurity { + certfile?: string; + keyfile?: string; + password?: string; + server_hostname?: string; +} + +export interface ModbusSlave { + host?: string; + type: ModbusProtocolType; + method: ModbusMethodType; + unitId: number; + baudrate?: number; + deviceName: string; + deviceType: string; + pollPeriod: number; + sendDataToThingsBoard: boolean; + byteOrder: ModbusOrderType; + identity: ModbusIdentity; + values: ModbusRegisterValues; + port: string | number; + security: ModbusSecurity; +} + +export interface ModbusRegisterValues { + attributes: ModbusValue[]; + timeseries: ModbusValue[]; + attributeUpdates: ModbusValue[]; + rpc: ModbusValue[]; +} + +export interface ModbusIdentity { + vendorName?: string; + productCode?: string; + vendorUrl?: string; + productName?: string; + modelName?: string; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 1d28f139c0..3c2b7affc1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -90,9 +90,6 @@ import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/ import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/chart/time-series-chart-widget.component'; import { AddConnectorDialogComponent } from '@home/components/widget/lib/gateway/dialog/add-connector-dialog.component'; import { MappingDialogComponent } from '@home/components/widget/lib/gateway/dialog/mapping-dialog.component'; -import { - EllipsisChipListDirective -} from '@home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive'; import { StatusWidgetComponent } from '@home/components/widget/lib/indicator/status-widget.component'; import { LatestChartComponent } from '@home/components/widget/lib/chart/latest-chart.component'; import { PieChartWidgetComponent } from '@home/components/widget/lib/chart/pie-chart-widget.component'; @@ -120,10 +117,12 @@ import { MappingTableComponent, MqttBasicConfigComponent, OpcUaBasicConfigComponent, - ServerConfigComponent, + OpcServerConfigComponent, TypeValuePanelComponent, WorkersConfigControlComponent, + ModbusBasicConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; +import { EllipsisChipListDirective } from '@shared/directives/public-api'; @NgModule({ declarations: [ @@ -163,7 +162,6 @@ import { GatewayConfigurationComponent, GatewayRemoteConfigurationDialogComponent, GatewayServiceRPCConnectorTemplateDialogComponent, - EllipsisChipListDirective, ValueCardWidgetComponent, AggregatedValueCardWidgetComponent, CountWidgetComponent, @@ -193,22 +191,24 @@ import { LabelValueCardWidgetComponent, UnreadNotificationWidgetComponent, NotificationTypeFilterPanelComponent], - imports: [ - CommonModule, - SharedModule, - RpcWidgetsModule, - HomePageWidgetsModule, - SharedHomeComponentsModule, - RestConnectorSecurityComponent, - GatewayHelpLinkPipe, - BrokerConfigControlComponent, - WorkersConfigControlComponent, - ServerConfigComponent, - MqttBasicConfigComponent, - MappingTableComponent, - OpcUaBasicConfigComponent, - KeyValueIsNotEmptyPipe - ], + imports: [ + CommonModule, + SharedModule, + RpcWidgetsModule, + HomePageWidgetsModule, + SharedHomeComponentsModule, + RestConnectorSecurityComponent, + GatewayHelpLinkPipe, + BrokerConfigControlComponent, + WorkersConfigControlComponent, + OpcServerConfigComponent, + MqttBasicConfigComponent, + MappingTableComponent, + OpcUaBasicConfigComponent, + KeyValueIsNotEmptyPipe, + ModbusBasicConfigComponent, + EllipsisChipListDirective, + ], exports: [ EntitiesTableWidgetComponent, AlarmsTableWidgetComponent, diff --git a/ui-ngx/src/app/modules/home/pipes/gateway-port-tooltip/gateway-port-tooltip.pipe.ts b/ui-ngx/src/app/modules/home/pipes/gateway-port-tooltip/gateway-port-tooltip.pipe.ts new file mode 100644 index 0000000000..9ff3e134c6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pipes/gateway-port-tooltip/gateway-port-tooltip.pipe.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Pipe, PipeTransform } from '@angular/core'; +import { PortLimits, } from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { AbstractControl } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; + +@Pipe({ + name: 'getGatewayPortTooltip', + standalone: true, +}) +export class GatewayPortTooltipPipe implements PipeTransform { + + constructor(private translate: TranslateService) {} + + transform(portControl: AbstractControl): string { + if (portControl.hasError('required')) { + return this.translate.instant('gateway.port-required'); + } else if ( + portControl.hasError('min') || + portControl.hasError('max') + ) { + return this.translate.instant('gateway.port-limits-error', + {min: PortLimits.MIN, max: PortLimits.MAX}); + } + return ''; + } +} diff --git a/ui-ngx/src/app/modules/home/pipes/public-api.ts b/ui-ngx/src/app/modules/home/pipes/public-api.ts index 1cb3ce312a..9db71f9a06 100644 --- a/ui-ngx/src/app/modules/home/pipes/public-api.ts +++ b/ui-ngx/src/app/modules/home/pipes/public-api.ts @@ -15,3 +15,4 @@ /// export * from './gateway-help-link/gateway-help-link.pipe'; +export * from './gateway-port-tooltip/gateway-port-tooltip.pipe'; diff --git a/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts b/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts new file mode 100644 index 0000000000..fd8808c9cf --- /dev/null +++ b/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts @@ -0,0 +1,32 @@ +import { DataSource } from '@angular/cdk/collections'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export abstract class TbDatasource implements DataSource { + + protected dataSubject = new BehaviorSubject>([]); + + connect(): Observable> { + return this.dataSubject.asObservable(); + } + + disconnect(): void { + this.dataSubject.complete(); + } + + loadData(data: Array): void { + this.dataSubject.next(data); + } + + isEmpty(): Observable { + return this.dataSubject.pipe( + map((data: DataType[]) => !data.length) + ); + } + + total(): Observable { + return this.dataSubject.pipe( + map((data: DataType[]) => data.length) + ); + } +} diff --git a/ui-ngx/src/app/shared/abstract/public-api.ts b/ui-ngx/src/app/shared/abstract/public-api.ts new file mode 100644 index 0000000000..4345e3f4f2 --- /dev/null +++ b/ui-ngx/src/app/shared/abstract/public-api.ts @@ -0,0 +1 @@ +export * from './datasource/datasource.abstract'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts b/ui-ngx/src/app/shared/directives/ellipsis-chip-list/ellipsis-chip-list.directive.ts similarity index 89% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts rename to ui-ngx/src/app/shared/directives/ellipsis-chip-list/ellipsis-chip-list.directive.ts index aa4738d707..4c44203b64 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/ellipsis-chip-list.directive.ts +++ b/ui-ngx/src/app/shared/directives/ellipsis-chip-list/ellipsis-chip-list.directive.ts @@ -20,7 +20,8 @@ import { Inject, Input, OnDestroy, - Renderer2 + Renderer2, + AfterViewInit, } from '@angular/core'; import { isEqual } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; @@ -30,13 +31,15 @@ import { takeUntil } from 'rxjs/operators'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector - selector: '[tb-ellipsis-chip-list]' + selector: '[tb-ellipsis-chip-list]', + standalone: true, }) -export class EllipsisChipListDirective implements OnDestroy { +export class EllipsisChipListDirective implements OnDestroy, AfterViewInit { chipsValue: string[]; private destroy$ = new Subject(); + private intersectionObserver: IntersectionObserver; @Input('tb-ellipsis-chip-list') set chips(value: string[]) { @@ -59,6 +62,18 @@ export class EllipsisChipListDirective implements OnDestroy { ).subscribe(() => { this.adjustChips(); }); + + this.intersectionObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + this.adjustChips(); + } + }); + }); + } + + ngAfterViewInit(): void { + this.intersectionObserver.observe(this.el.nativeElement); } private adjustChips(): void { @@ -124,5 +139,6 @@ export class EllipsisChipListDirective implements OnDestroy { ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); + this.intersectionObserver.disconnect(); } } diff --git a/ui-ngx/src/app/shared/directives/public-api.ts b/ui-ngx/src/app/shared/directives/public-api.ts new file mode 100644 index 0000000000..0e94de2a78 --- /dev/null +++ b/ui-ngx/src/app/shared/directives/public-api.ts @@ -0,0 +1 @@ +export * from './ellipsis-chip-list/ellipsis-chip-list.directive'; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 33c8d25652..6517f2214f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2755,19 +2755,27 @@ "function": "Function" }, "gateway": { + "address": "Address", "add-entry": "Add configuration", "add-attribute": "Add attribute", "add-attribute-update": "Add attribute update", "add-key": "Add key", "add-timeseries": "Add time series", + "add-telementry": "Add telementry", "add-mapping": "Add mapping", + "add-slave": "Add Slave", "arguments": "Arguments", "add-rpc-method": "Add method", + "add-rpc-request": "Add request", "add-value": "Add argument", + "baudrate": "Baudrate", + "bytesize": "Bytesize", "delete-value": "Delete value", "delete-rpc-method": "Delete method", + "delete-rpc-request": "Delete request", "delete-attribute-update": "Add attribute update", "advanced": "Advanced", + "advanced-connection-settings": "Advanced connection settings", "attributes": "Attributes", "attribute-updates": "Attribute updates", "attribute-filter": "Attribute filter", @@ -2777,6 +2785,8 @@ "attribute-name-expression-required": "Attribute name expression required.", "attribute-name-expression-hint": "Hint for Attribute name expression", "basic": "Basic", + "byte-order": "Byte order", + "word-order": "Word order", "broker": { "connection": "Connection to broker", "name": "Broker name", @@ -2811,6 +2821,9 @@ "connectors-table-actions": "Actions", "connectors-table-key": "Key", "connectors-table-class": "Class", + "connection-timeout": "Connection timeout (s)", + "connect-attempt-time": "Connect attempt time (s)", + "connect-attempt-count": "Connect attempt count", "copy-username": "Copy username", "copy-password": "Copy password", "copy-client-id": "Copy client ID", @@ -2840,6 +2853,7 @@ "device-name-filter-required": "Device name filter is required.", "details": "Details", "delete-mapping-title": "Delete mapping ?", + "delete-slave-title": "Delete slave ?", "download-configuration-file": "Download configuration file", "download-docker-compose": "Download docker-compose.yml for your gateway", "enable-remote-logging": "Enable remote logging", @@ -2858,6 +2872,7 @@ "configuration-delete-dialog-confirm": "Turn Off", "connector-duplicate-name": "Connector with such name already exists.", "connector-side": "Connector side", + "client-communication-type": "Client communication type", "payload-type": "Payload type", "platform-side": "Platform side", "JSON": "JSON", @@ -2877,6 +2892,7 @@ "delete-attribute": "Delete attribute", "delete-key": "Delete key", "delete-timeseries": "Delete time series", + "delete-telementry": "Delete telementry", "default": "Default", "device-node": "Device node", "device-node-required": "Device node required.", @@ -2895,6 +2911,7 @@ "fill-connector-defaults-hint": "This property allows to fill connector configuration with default values on it's creation.", "from-device-request-settings": "Input request parsing", "from-device-request-settings-hint": "These fields support JSONPath expressions to extract a name from incoming message.", + "function-code": "Function code", "to-device-response-settings": "Output request processing", "to-device-response-settings-hint": "For these fields you can use the following variables and they will be replaced with actual values: ${deviceName}, ${attributeKey}, ${attributeValue}", "gateway": "Gateway", @@ -2933,8 +2950,13 @@ "inactivity-timeout-seconds-required": "Inactivity timeout is required", "inactivity-timeout-seconds-min": "Inactivity timeout can not be less then 1", "inactivity-timeout-seconds-pattern": "Inactivity timeout is not valid", + "unit-id": "Unit ID", "host": "Host", "host-required": "Host is required.", + "holding_registers": "Holding register", + "coils_initializer": "Coils initializer", + "input_registers": "Input register", + "discrete_inputs": "Discrete input", "json-parse": "Not valid JSON.", "json-required": "Field cannot be empty.", "JSONPath-hint": "This field supports constants and JSONPath expressions.", @@ -2968,12 +2990,14 @@ "max-messages-queue-for-worker": "Max messages queue per worker", "max-messages-queue-for-worker-hint": "Maximal messages count that will be in the queue \nfor each converter worker.", "max-messages-queue-for-worker-required": "Max messages queue per worker is required.", + "method": "Method", "method-name": "Method name", "method-required": "Method name is required.", "min-pack-send-delay": "Min pack send delay (in ms)", "min-pack-send-delay-required": "Min pack send delay is required", "min-pack-send-delay-min": "Min pack send delay can not be less then 0", "mode": "Mode", + "model-name": "Model name", "mqtt-version": "MQTT version", "name": "Name", "name-required": "Name is required.", @@ -2984,20 +3008,26 @@ "no-gateway-found": "No gateway found.", "no-gateway-matching": " '{{item}}' not found.", "no-timeseries": "No time series", + "no-telementry": "No telementry", "no-keys": "No keys", "no-value": "No arguments", "no-rpc-methods": "No RPC methods", + "no-rpc-requests": "No RPC requests", "path-hint": "The path is local to the gateway file system", "path-logs": "Path to log files", "path-logs-required": "Path is required.", "password": "Password", "password-required": "Password is required.", "permit-without-calls": "Keep alive permit without calls", + "poll-period": "Poll period (s)", "port": "Port", "port-required": "Port is required.", "port-limits-error": "Port should be number from {{min}} to {{max}}.", "private-key-path": "Path to private key file", "path-to-private-key-required": "Path to private key file is required.", + "parity": "Parity", + "product-code": "Product code", + "product-name": "Product name", "raw": "Raw", "retain": "Retain", "retain-hint": "This flag tells the broker to store the message for a topic\nand ensures any new client subscribing to that topic\nwill receive the stored message.", @@ -3006,6 +3036,9 @@ "remove-entry": "Remove configuration", "remote-shell": "Remote shell", "remote-configuration": "Remote Configuration", + "retries": "Retries", + "retries-on-empty": "Retries on empty", + "retries-on-invalid": "Retries on invalid", "rpc": { "title": "{{type}} Connector RPC parameters", "templates-title": "Connector RPC Templates", @@ -3097,6 +3130,7 @@ "json-value-invalid": "JSON value has an invalid format" }, "rpc-methods": "RPC methods", + "rpc-requests": "RPC requests", "request" : { "connect-request": "Connect request", "disconnect-request": "Disconnect request", @@ -3108,6 +3142,7 @@ "requests-mapping": "Requests mapping", "requests-mapping-hint": "MQTT Connector requests allows you to connect, disconnect, process attribute requests from the device, handle attribute updates on the server and RPC processing configuration.", "request-topic-expression": "Request topic expression", + "request-client-certificate": "Request client certificate", "request-topic-expression-required": "Request topic expression is required.", "response-timeout": "Response timeout (ms)", "response-timeout-required": "Response timeout is required.", @@ -3118,7 +3153,10 @@ "response-topic-expression-required": "Response topic expression is required.", "response-value-expression": "Response value expression", "response-value-expression-required": "Response value expression is required.", + "vendor-name": "Vendor name", + "vendor-url": "Vendor URL", "value": "Value", + "values": "Values", "value-required": "Value is required.", "value-expression": "Value expression", "value-expression-required": "Value expression is required.", @@ -3141,11 +3179,21 @@ }, "select-connector": "Select connector to display config", "send-change-data": "Send data only on change", + "send-data-TB": "Send data to ThingsBoard", + "send-data-on-change": "Send data only on change", "send-change-data-hint": "The values will be saved to the database only if they are different from the corresponding values in the previous converted message. This functionality applies to both attributes and time series in the converter output.", "server": "Server", + "server-hostname": "Server hostname", + "server-slave": "Server (Slave)", + "servers-slaves": "Servers (Slaves)", "server-port": "Server port", "server-url": "Server endpoint url", + "server-connection": "Server Connection", + "server-config": "Server configuration", + "server-slave-config": "Server (Slave) configuration", "server-url-required": "Server endpoint url is required.", + "stopbits": "Stopbits", + "strict": "Strict", "set": "Set", "show-map": "Show map", "statistics": { @@ -3217,6 +3265,7 @@ "thingsboard": "ThingsBoard", "general": "General", "timeseries": "Time series", + "telementry": "Telementry", "key": "Key", "keys": "Keys", "key-required": "Key is required.", @@ -3237,6 +3286,8 @@ "topic-required": "Topic filter is required.", "tls-path-ca-certificate": "Path to CA certificate on gateway", "tls-path-client-certificate": "Path to client certificate on gateway", + "tls-connection": "TLS Connection", + "master-connections": "Master Connections", "method-filter": "Method filter", "method-filter-hint": "Regular expression to filter incoming RPC method from platform.", "method-filter-required": "Method filter is required.", @@ -3256,12 +3307,22 @@ "at-least-once": "1 - At least once", "exactly-once": "2 - Exactly once" }, + "objects-count": "Objects count", + "wait-after-failed-attempts": "Wait after failed attempts (s)", "tls-path-private-key": "Path to private key on gateway", "toggle-fullscreen": "Toggle fullscreen", "transformer-json-config": "Configuration JSON*", "update-config": "Add/update configuration JSON", "username": "Username", "username-required": "Username is required.", + "read-coils": "Read Coils", + "read-discrete-inputs": "Read Discrete Inputs", + "read-multiple-holding-registers": "Read Multiple Holding Register", + "read-input-registers": "Read Input Registers", + "write-coil": "Write Coil", + "write-coils": "Write Coils", + "write-register": "Write Register", + "write-registers": "Write Registers", "hints": { "remote-configuration": "Enables remote configuration and management of the gateway", "remote-shell": "Enables remote control of the operating system with the gateway from the Remote Shell widget", @@ -3302,6 +3363,7 @@ "grpc-min-ping-interval-without-data": "Minimum amount of time the server should wait between sending keepalive ping messages when there is no data being sent or received.", "permit-without-calls": "Allow server to keep the GRPC connection alive even when there are no active RPC calls.", "memory": "Your data will be stored in the in-memory queue, it is a fastest but no persistence guarantee.", + "framer-type": "Type of framer.", "file": "Your data will be stored in separated files and will be saved even after the gateway restart.", "sqlite": "Your data will be stored in file based database. And will be saved even after the gateway restart.", "opcua-timeout": "Timeout in seconds for connecting to OPC-UA server.", diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json index 437a6abd95..fee4e745d6 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json @@ -2,6 +2,7 @@ "master": { "slaves": [ { + "name": "Slave 1", "host": "127.0.0.1", "port": 5021, "type": "tcp", @@ -185,64 +186,60 @@ "wordOrder": "LITTLE", "unitId": 0, "values": { - "holding_registers": [ - { - "attributes": [ - { - "address": 1, - "type": "string", - "tag": "sm", - "objectsCount": 1, - "value": "ON" - } - ], - "timeseries": [ - { - "address": 2, - "type": "int", - "tag": "smm", - "objectsCount": 1, - "value": "12334" - } - ], - "attributeUpdates": [ - { - "tag": "shared_attribute_write", - "type": "32int", - "functionCode": 6, - "objectsCount": 2, - "address": 29, - "value": 1243 - } - ], - "rpc": [ - { - "tag": "setValue", - "type": "bits", - "functionCode": 5, - "objectsCount": 1, - "address": 31, - "value": 22 - } - ] + "holding_registers": { + "attributes": [ + { + "address": 1, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "ON" + } + ], + "timeseries": [ + { + "address": 2, + "type": "int", + "tag": "smm", + "objectsCount": 1, + "value": "12334" + } + ], + "attributeUpdates": [ + { + "tag": "shared_attribute_write", + "type": "32int", + "functionCode": 6, + "objectsCount": 2, + "address": 29, + "value": 1243 + } + ], + "rpc": [ + { + "tag": "setValue", + "type": "bits", + "functionCode": 5, + "objectsCount": 1, + "address": 31, + "value": 22 + } + ] + }, + "coils_initializer": { + "attributes": [ + { + "address": 5, + "type": "string", + "tag": "sm", + "objectsCount": 1, + "value": "12" + } + ], + "timeseries": [], + "attributeUpdates": [], + "rpc": [] } - ], - "coils_initializer": [ - { - "attributes": [ - { - "address": 5, - "type": "string", - "tag": "sm", - "objectsCount": 1, - "value": "12" - } - ], - "timeseries": [], - "attributeUpdates": [], - "rpc": [] - } - ] } } } From 7ade2b20ed1e61bcb9bdfe6ec098cf24efcc814d Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 18 Jul 2024 18:41:05 +0300 Subject: [PATCH 2/5] refactoring --- .../connectors-configuration/modbus/index.ts | 7 ------- .../modbus-basic-config.component.ts | 7 +++---- .../modbus-master-table.component.ts | 2 +- .../modbus-security-config.component.html | 17 +++++++++++++++++ .../modbus-slave-config.component.ts | 6 ++---- .../modbus-slave-dialog.component.ts | 6 ++---- .../modbus-values/modbus-values.component.ts | 2 +- .../modbus/public-api.ts | 16 ++++++++++++++++ .../abstract/datasource/datasource.abstract.ts | 16 ++++++++++++++++ ui-ngx/src/app/shared/abstract/public-api.ts | 16 ++++++++++++++++ ui-ngx/src/app/shared/directives/public-api.ts | 16 ++++++++++++++++ 11 files changed, 90 insertions(+), 21 deletions(-) delete mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/index.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/index.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/index.ts deleted file mode 100644 index 803a8ff8dd..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './modbus-basic-config/modbus-basic-config.component'; -export * from './modbus-values/modbus-values.component'; -export * from './modbus-data-keys-panel/modbus-data-keys-panel.component'; -export * from './modbus-slave-config/modbus-slave-config.component'; -export * from './modbus-master-table/modbus-master-table.component'; -export * from './modbus-slave-dialog/modbus-slave-dialog.component'; -export * from './modbus-security-config/modbus-security-config.component'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts index d61d63edb8..8499aa2512 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts @@ -32,11 +32,10 @@ import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; -import { - ModbusMasterTableComponent, - ModbusSlaveConfigComponent, -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus'; + import { EllipsisChipListDirective } from '@shared/directives/public-api'; +import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component'; +import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component'; @Component({ selector: 'tb-modbus-basic-config', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts index b1f2c09689..69613b9207 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts @@ -46,7 +46,7 @@ import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { TbDatasource } from '@shared/abstract/datasource/datasource.abstract'; -import { ModbusSlaveDialogComponent } from '@home/components/widget/lib/gateway/connectors-configuration/modbus'; +import { ModbusSlaveDialogComponent } from '../modbus-slave-dialog/modbus-slave-dialog.component'; @Component({ selector: 'tb-modbus-master-table', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html index 900973d327..0f30dc4314 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html @@ -1,3 +1,20 @@ +
gateway.client-cert-path
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts index 3bb21cd9d4..903e449d86 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts @@ -38,11 +38,9 @@ import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { - ModbusValuesComponent, - ModbusSecurityConfigComponent -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus'; import { GatewayPortTooltipPipe } from '@home/pipes/public-api'; +import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; +import { ModbusValuesComponent, } from '../modbus-values/modbus-values.component'; @Component({ selector: 'tb-modbus-server-config', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts index 202ef260c7..b4b95d8b72 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts @@ -36,10 +36,8 @@ import { import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { Subject } from 'rxjs'; -import { - ModbusValuesComponent, - ModbusSecurityConfigComponent, -} from '@home/components/widget/lib/gateway/connectors-configuration/modbus'; +import { ModbusValuesComponent } from '../modbus-values/modbus-values.component'; +import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; import { DialogComponent } from '@shared/components/dialog.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts index 76b376fb1b..57acf4a21c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts @@ -46,12 +46,12 @@ import { } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; -import { ModbusDataKeysPanelComponent } from '@home/components/widget/lib/gateway/connectors-configuration/public-api'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; import { EllipsisChipListDirective } from '@shared/directives/public-api'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; +import { ModbusDataKeysPanelComponent } from '../modbus-data-keys-panel/modbus-data-keys-panel.component'; @Component({ selector: 'tb-modbus-values', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/public-api.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/public-api.ts index 803a8ff8dd..87a3ba5c2f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/public-api.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/public-api.ts @@ -1,3 +1,19 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + export * from './modbus-basic-config/modbus-basic-config.component'; export * from './modbus-values/modbus-values.component'; export * from './modbus-data-keys-panel/modbus-data-keys-panel.component'; diff --git a/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts b/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts index fd8808c9cf..2f1d9b1d78 100644 --- a/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts +++ b/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts @@ -1,3 +1,19 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + import { DataSource } from '@angular/cdk/collections'; import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; diff --git a/ui-ngx/src/app/shared/abstract/public-api.ts b/ui-ngx/src/app/shared/abstract/public-api.ts index 4345e3f4f2..562011065a 100644 --- a/ui-ngx/src/app/shared/abstract/public-api.ts +++ b/ui-ngx/src/app/shared/abstract/public-api.ts @@ -1 +1,17 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + export * from './datasource/datasource.abstract'; diff --git a/ui-ngx/src/app/shared/directives/public-api.ts b/ui-ngx/src/app/shared/directives/public-api.ts index 0e94de2a78..f1fae708d0 100644 --- a/ui-ngx/src/app/shared/directives/public-api.ts +++ b/ui-ngx/src/app/shared/directives/public-api.ts @@ -1 +1,17 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + export * from './ellipsis-chip-list/ellipsis-chip-list.directive'; From e87f00957f8ec2f56947edd57c9c7e04f6e0fc07 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Fri, 19 Jul 2024 11:39:02 +0300 Subject: [PATCH 3/5] review comments adjustments --- .../modbus-basic-config.component.ts | 7 ++--- .../modbus-data-keys-panel.component.html | 13 ++++++++-- .../modbus-slave-config.component.html | 13 +++++++++- .../modbus-slave-dialog.component.html | 24 +++++++++++++++-- .../modbus-slave-dialog.component.ts | 2 +- .../mqtt-basic-config.component.ts | 4 +-- .../opc-ua-basic-config.component.ts | 4 +-- .../gateway/gateway-connectors.component.ts | 3 ++- .../lib/gateway/gateway-widget.models.ts | 26 ++++++++++++------- .../datasource/datasource.abstract.ts | 12 ++++----- .../ellipsis-chip-list.directive.ts | 5 +--- .../assets/locale/locale.constant-en_US.json | 4 +-- 12 files changed, 80 insertions(+), 37 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts index 8499aa2512..6f2d9aff02 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts @@ -24,10 +24,7 @@ import { ValidationErrors, Validator, } from '@angular/forms'; -import { - ConnectorBaseConfig, - ConnectorType, -} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { ConnectorType, ModbusBasicConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { takeUntil } from 'rxjs/operators'; @@ -112,7 +109,7 @@ export class ModbusBasicConfigComponent implements ControlValueAccessor, Validat this.onTouched = fn; } - writeValue(basicConfig: ConnectorBaseConfig): void { + writeValue(basicConfig: ModbusBasicConfig): void { const editedBase = { slave: basicConfig.slave || {}, master: basicConfig.master || {}, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html index 08ef83eeb5..1101565275 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html @@ -33,7 +33,7 @@
-
+
gateway.key
@@ -98,10 +98,19 @@
-
gateway.value
+
gateway.value
+ + warning +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html index 43c71958bf..bfc45325e4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html @@ -68,10 +68,21 @@
-
gateway.port
+
gateway.port
+ + warning +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html index 5c8fb4c397..80d5d6115b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html @@ -28,10 +28,19 @@
-
gateway.name
+
gateway.name
+ + warning +
@@ -89,10 +98,21 @@
-
gateway.port
+
gateway.port
+ + warning +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts index b4b95d8b72..3f12692dcb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts @@ -118,7 +118,7 @@ export class ModbusSlaveDialogComponent extends DialogComponent | RequestMappingData[]; - server?: ServerConfig; - broker?: BrokerConfig; - workers?: WorkersConfig; - master?: ModbusMasterConfig; - slave?: ModbusSlave; +export type ConnectorBaseConfig = MQTTBasicConfig | OPCBasicConfig | ModbusBasicConfig; + +export interface MQTTBasicConfig { + dataMapping: ConverterConnectorMapping[]; + requestsMapping: Record | RequestMappingData[]; + broker: BrokerConfig; + workers: WorkersConfig; +} + +export interface OPCBasicConfig { + mapping: DeviceConnectorMapping[]; + server: ServerConfig; +} + +export interface ModbusBasicConfig { + master: ModbusMasterConfig; + slave: ModbusSlave; } export interface WorkersConfig { diff --git a/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts b/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts index 2f1d9b1d78..43de3789a0 100644 --- a/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts +++ b/ui-ngx/src/app/shared/abstract/datasource/datasource.abstract.ts @@ -18,11 +18,11 @@ import { DataSource } from '@angular/cdk/collections'; import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -export abstract class TbDatasource implements DataSource { +export abstract class TbDatasource implements DataSource { - protected dataSubject = new BehaviorSubject>([]); + protected dataSubject = new BehaviorSubject>([]); - connect(): Observable> { + connect(): Observable> { return this.dataSubject.asObservable(); } @@ -30,19 +30,19 @@ export abstract class TbDatasource implements DataSource { this.dataSubject.complete(); } - loadData(data: Array): void { + loadData(data: Array): void { this.dataSubject.next(data); } isEmpty(): Observable { return this.dataSubject.pipe( - map((data: DataType[]) => !data.length) + map((data: T[]) => !data.length) ); } total(): Observable { return this.dataSubject.pipe( - map((data: DataType[]) => data.length) + map((data: T[]) => data.length) ); } } diff --git a/ui-ngx/src/app/shared/directives/ellipsis-chip-list/ellipsis-chip-list.directive.ts b/ui-ngx/src/app/shared/directives/ellipsis-chip-list/ellipsis-chip-list.directive.ts index 4c44203b64..bb2847aeee 100644 --- a/ui-ngx/src/app/shared/directives/ellipsis-chip-list/ellipsis-chip-list.directive.ts +++ b/ui-ngx/src/app/shared/directives/ellipsis-chip-list/ellipsis-chip-list.directive.ts @@ -21,7 +21,6 @@ import { Input, OnDestroy, Renderer2, - AfterViewInit, } from '@angular/core'; import { isEqual } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; @@ -34,7 +33,7 @@ import { takeUntil } from 'rxjs/operators'; selector: '[tb-ellipsis-chip-list]', standalone: true, }) -export class EllipsisChipListDirective implements OnDestroy, AfterViewInit { +export class EllipsisChipListDirective implements OnDestroy { chipsValue: string[]; @@ -70,9 +69,7 @@ export class EllipsisChipListDirective implements OnDestroy, AfterViewInit { } }); }); - } - ngAfterViewInit(): void { this.intersectionObserver.observe(this.el.nativeElement); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 6517f2214f..2b89fd45ee 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2852,8 +2852,8 @@ "device-name-filter-hint": "This field supports Regular expressions to filter incoming data by device name.", "device-name-filter-required": "Device name filter is required.", "details": "Details", - "delete-mapping-title": "Delete mapping ?", - "delete-slave-title": "Delete slave ?", + "delete-mapping-title": "Delete mapping?", + "delete-slave-title": "Delete slave?", "download-configuration-file": "Download configuration file", "download-docker-compose": "Download docker-compose.yml for your gateway", "enable-remote-logging": "Enable remote logging", From 8b0a2012eec75ae009df3429d94c1bc3f9fe2417 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Fri, 19 Jul 2024 12:32:48 +0300 Subject: [PATCH 4/5] minor adjustments --- .../modbus-data-keys-panel.component.html | 152 +++++++++--------- .../modbus-data-keys-panel.component.ts | 3 +- .../modbus-slave-config.component.html | 22 ++- .../modbus-slave-config.component.ts | 4 +- .../modbus-slave-dialog.component.html | 22 ++- .../modbus-slave-dialog.component.ts | 6 +- .../modbus-values.component.html | 8 +- .../lib/gateway/gateway-widget.models.ts | 10 +- .../assets/locale/locale.constant-en_US.json | 6 +- 9 files changed, 137 insertions(+), 96 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html index 1101565275..efa8e11019 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html @@ -27,91 +27,97 @@
- {{ keyControl.get('tag').value }} + {{ keyControl.get('tag').value }}{{ '-' }}{{ keyControl.get('value').value }}
-
-
- gateway.key -
-
- - - +
gateway.platform-side
+
+
+ gateway.key +
+
+ + + - warning - - + class="tb-error"> + warning + + +
-
-
- gateway.type -
-
- - - {{ type }} - - +
+
gateway.connector-side
+
+
+ gateway.type +
+
+ + + {{ type }} + + +
-
-
-
gateway.function-code
-
- - - {{ ModbusFunctionCodeTranslationsMap.get(code) | translate }} - - +
+
gateway.function-code
+
+ + + {{ ModbusFunctionCodeTranslationsMap.get(code) | translate }} + + +
-
-
-
gateway.objects-count
-
- - - +
+
gateway.objects-count
+
+ + + +
-
-
-
gateway.address
-
- - - +
+
gateway.address
+
+ + + +
-
-
-
gateway.value
-
- - - +
gateway.value
+
+ + + - warning - - + class="tb-error"> + warning + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts index a63b2e488e..81e275b07c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts @@ -18,6 +18,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { AbstractControl, FormGroup, UntypedFormArray, UntypedFormBuilder, Validators } from '@angular/forms'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { + MappingKeysType, ModbusDataType, ModbusFunctionCodeTranslationsMap, ModbusObjectCountByDataType, @@ -71,7 +72,7 @@ export class ModbusDataKeysPanelComponent implements OnInit { ngOnInit(): void { this.keysListFormArray = this.prepareKeysFormArray(this.values); - this.withFunctionCode = !this.isMaster || (this.keysType !== ModbusValueKey.ATTRIBUTES && this.keysType !== ModbusValueKey.TELEMENTRY); + this.withFunctionCode = !this.isMaster || (this.keysType !== ModbusValueKey.ATTRIBUTES && this.keysType !== ModbusValueKey.TIMESERIES); this.defaultFunctionCodes = this.getDefaultFunctionCodes(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html index bfc45325e4..5587cd09ee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html @@ -109,18 +109,36 @@
-
gateway.device-name
+
gateway.device-name
+ + warning +
-
gateway.device-profile
+
gateway.device-profile
+ + warning +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts index 903e449d86..4211b2b09c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts @@ -106,8 +106,8 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat method: [ModbusMethodType.SOCKET, []], unitId: [null, []], baudrate: [null, []], - deviceName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], - deviceType: ['', [Validators.pattern(noLeadTrailSpacesRegex)]], + deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], pollPeriod: [null, []], sendDataToThingsBoard: [false, []], byteOrder:[ModbusOrderType.BIG, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html index 80d5d6115b..95e7700886 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html @@ -180,18 +180,36 @@
-
gateway.device-name
+
gateway.device-name
+ + warning +
-
gateway.device-profile
+
gateway.device-profile
+ + warning +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts index 3f12692dcb..991c71c16b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts @@ -114,7 +114,7 @@ export class ModbusSlaveDialogComponent extends DialogComponent
-
gateway.telementry
+
gateway.timeseries
- - + + {{ telemetry.tag }} @@ -68,7 +68,7 @@ mat-icon-button color="primary" #telemetryButton - (click)="manageKeys($event, telemetryButton, ModbusValueKey.TELEMENTRY, register)"> + (click)="manageKeys($event, telemetryButton, ModbusValueKey.TIMESERIES, register)"> edit
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index 865f819315..42a58b784e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -844,7 +844,7 @@ export const ModbusFieldsTranslationsMap = new Map( export enum ModbusValueKey { ATTRIBUTES = 'attributes', - TELEMENTRY = 'timeseries', + TIMESERIES = 'timeseries', ATTRIBUTES_UPDATES = 'attributeUpdates', RPC_REQUESTS = 'rpc', } @@ -852,7 +852,7 @@ export enum ModbusValueKey { export const ModbusKeysPanelTitleTranslationsMap = new Map( [ [ModbusValueKey.ATTRIBUTES, 'gateway.attributes'], - [ModbusValueKey.TELEMENTRY, 'gateway.telementry'], + [ModbusValueKey.TIMESERIES, 'gateway.timeseries'], [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.attribute-updates'], [ModbusValueKey.RPC_REQUESTS, 'gateway.rpc-requests'] ] @@ -861,7 +861,7 @@ export const ModbusKeysPanelTitleTranslationsMap = new Map( [ [ModbusValueKey.ATTRIBUTES, 'gateway.add-attribute'], - [ModbusValueKey.TELEMENTRY, 'gateway.add-telementry'], + [ModbusValueKey.TIMESERIES, 'gateway.add-timeseries'], [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.add-attribute-update'], [ModbusValueKey.RPC_REQUESTS, 'gateway.add-rpc-request'] ] @@ -870,7 +870,7 @@ export const ModbusKeysAddKeyTranslationsMap = new Map( export const ModbusKeysDeleteKeyTranslationsMap = new Map( [ [ModbusValueKey.ATTRIBUTES, 'gateway.delete-attribute'], - [ModbusValueKey.TELEMENTRY, 'gateway.delete-telementry'], + [ModbusValueKey.TIMESERIES, 'gateway.delete-timeseries'], [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.delete-attribute-update'], [ModbusValueKey.RPC_REQUESTS, 'gateway.delete-rpc-requests'] ] @@ -879,7 +879,7 @@ export const ModbusKeysDeleteKeyTranslationsMap = new Map( [ [ModbusValueKey.ATTRIBUTES, 'gateway.no-attributes'], - [ModbusValueKey.TELEMENTRY, 'gateway.no-telementry'], + [ModbusValueKey.TIMESERIES, 'gateway.no-timeseries'], [ModbusValueKey.ATTRIBUTES_UPDATES, 'gateway.no-attribute-updates'], [ModbusValueKey.RPC_REQUESTS, 'gateway.no-rpc-requests'] ] diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 2b89fd45ee..0475c771e6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2761,7 +2761,6 @@ "add-attribute-update": "Add attribute update", "add-key": "Add key", "add-timeseries": "Add time series", - "add-telementry": "Add telementry", "add-mapping": "Add mapping", "add-slave": "Add Slave", "arguments": "Arguments", @@ -2892,13 +2891,14 @@ "delete-attribute": "Delete attribute", "delete-key": "Delete key", "delete-timeseries": "Delete time series", - "delete-telementry": "Delete telementry", "default": "Default", "device-node": "Device node", "device-node-required": "Device node required.", "device-node-hint": "Path or identifier for device node on OPC UA server. Relative paths from it for attributes and time series can be used.", "device-name": "Device name", "device-profile": "Device profile", + "device-name-required": "Device name required", + "device-profile-required": "Device profile required", "download-tip": "Download configuration file", "drop-file": "Drop file here or", "enable-subscription": "Enable subscription", @@ -3008,7 +3008,6 @@ "no-gateway-found": "No gateway found.", "no-gateway-matching": " '{{item}}' not found.", "no-timeseries": "No time series", - "no-telementry": "No telementry", "no-keys": "No keys", "no-value": "No arguments", "no-rpc-methods": "No RPC methods", @@ -3265,7 +3264,6 @@ "thingsboard": "ThingsBoard", "general": "General", "timeseries": "Time series", - "telementry": "Telementry", "key": "Key", "keys": "Keys", "key-required": "Key is required.", From 62e2c8a6ad687f0ce033502aa317c27bb69590a5 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Fri, 19 Jul 2024 17:39:25 +0300 Subject: [PATCH 5/5] functionality adjustments --- .../modbus-data-keys-panel.component.html | 9 ++- .../modbus-data-keys-panel.component.ts | 25 +++++--- .../modbus-security-config.component.html | 3 +- .../modbus-security-config.component.ts | 15 ++++- .../modbus-slave-config.component.html | 10 +++ .../modbus-slave-config.component.ts | 64 ++++++++++++++++--- .../modbus-slave-dialog.component.html | 9 +++ .../modbus-slave-dialog.component.ts | 4 +- .../lib/gateway/gateway-widget.models.ts | 2 + .../assets/locale/locale.constant-en_US.json | 3 + 10 files changed, 119 insertions(+), 25 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html index efa8e11019..a38b7e4dda 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html @@ -27,7 +27,10 @@
- {{ keyControl.get('tag').value }}{{ '-' }}{{ keyControl.get('value').value }} + + {{ keyControl.get('tag').value }}{{ '-' }}{{ keyControl.get('value').value }} + + {{ keyControl.get('tag').value }}
@@ -73,7 +76,7 @@
- {{ ModbusFunctionCodeTranslationsMap.get(code) | translate }} + {{ ModbusFunctionCodeTranslationsMap.get(code) | translate }}
@@ -102,7 +105,7 @@
-
+
gateway.value
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts index 81e275b07c..ac448f703e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts @@ -18,7 +18,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { AbstractControl, FormGroup, UntypedFormArray, UntypedFormBuilder, Validators } from '@angular/forms'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { - MappingKeysType, ModbusDataType, ModbusFunctionCodeTranslationsMap, ModbusObjectCountByDataType, @@ -30,6 +29,7 @@ import { import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { GatewayHelpLinkPipe } from '@home/pipes/public-api'; +import { generateSecret } from '@core/utils'; @Component({ selector: 'tb-modbus-data-keys-panel', @@ -83,11 +83,12 @@ export class ModbusDataKeysPanelComponent implements OnInit { addKey(): void { const dataKeyFormGroup = this.fb.group({ tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + value: [{value: '', disabled: !this.isMaster}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], type: [ModbusDataType.STRING, [Validators.required]], address: [0, [Validators.required]], objectsCount: [1, [Validators.required]], - functionCode: [this.getDefaultFunctionCodes()[0]] + functionCode: [this.getDefaultFunctionCodes()[0]], + id: [{value: generateSecret(5), disabled: true}], }); this.observeKeyDataType(dataKeyFormGroup); @@ -117,14 +118,15 @@ export class ModbusDataKeysPanelComponent implements OnInit { const { tag, value, type, address, objectsCount, functionCode } = keyData; const dataKeyFormGroup = this.fb.group({ tag: [tag, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - value: [value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + value: [{value, disabled: !this.isMaster}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], type: [type, [Validators.required]], address: [address, [Validators.required]], objectsCount: [objectsCount, [Validators.required]], functionCode: [functionCode, []], + id: [{value: generateSecret(5), disabled: true}], }); this.observeKeyDataType(dataKeyFormGroup); - this.functionCodesMap.set(tag+address, this.getFunctionCodes(type)); + this.functionCodesMap.set(dataKeyFormGroup.get('id').value, this.getFunctionCodes(type)); keysControlGroups.push(dataKeyFormGroup); }); @@ -138,8 +140,7 @@ export class ModbusDataKeysPanelComponent implements OnInit { if (!this.editableDataTypes.includes(dataType)) { objectsCountControl.patchValue(ModbusObjectCountByDataType[dataType]); } - const keyId = keyFormGroup.get('tag').value + keyFormGroup.get('address').value; - this.functionCodesMap.set(keyId, this.getFunctionCodes(dataType)); + this.functionCodesMap.set(keyFormGroup.get('id').value, this.getFunctionCodes(dataType)); }); } @@ -147,7 +148,7 @@ export class ModbusDataKeysPanelComponent implements OnInit { if (this.keysType === ModbusValueKey.ATTRIBUTES_UPDATES) { return this.defaultWriteFunctionCodes; } - const functionCodes = this.defaultReadFunctionCodes; + const functionCodes = [...this.defaultReadFunctionCodes]; if (dataType === ModbusDataType.BITS) { const bitsFunctionCodes = [1, 2]; bitsFunctionCodes.forEach(code => functionCodes.push(code)); @@ -160,6 +161,12 @@ export class ModbusDataKeysPanelComponent implements OnInit { } private getDefaultFunctionCodes(): number[] { - return this.keysType === ModbusValueKey.ATTRIBUTES_UPDATES ? this.defaultWriteFunctionCodes : this.defaultReadFunctionCodes; + if (this.keysType === ModbusValueKey.ATTRIBUTES_UPDATES) { + return this.defaultWriteFunctionCodes; + } + if (this.keysType === ModbusValueKey.RPC_REQUESTS) { + return [...this.defaultReadFunctionCodes, ...this.defaultWriteFunctionCodes]; + } + return this.defaultReadFunctionCodes; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html index 0f30dc4314..66db8c5018 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.html @@ -15,7 +15,8 @@ limitations under the License. --> -
+
+
{{ 'gateway.hints.path-in-os' | translate }}
gateway.client-cert-path
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts index 2f5786882b..bf926d3454 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts @@ -112,7 +112,20 @@ export class ModbusSecurityConfigComponent implements ControlValueAccessor, Vali } writeValue(securityConfig: ModbusSecurity): void { - this.securityConfigFormGroup.patchValue(securityConfig, {emitEvent: false}); + const { certfile, password, keyfile, server_hostname } = securityConfig; + let securityState = { + certfile: certfile ?? '', + password: password ?? '', + keyfile: keyfile ?? '', + server_hostname: server_hostname?? '', + reqclicert: !!securityConfig.reqclicert, + }; + if (this.isMaster) { + securityState = { ...securityState, reqclicert: !!securityConfig.reqclicert }; + } else { + securityState = { ...securityState, server_hostname: server_hostname ?? '' }; + } + this.securityConfigFormGroup.reset(securityState, {emitEvent: false}); } private observeValueChanges(): void { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html index 5587cd09ee..13f31bdda5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.html @@ -16,6 +16,7 @@ -->
+
{{ 'gateway.hints.modbus-server' | translate }}
gateway.server-slave-config
@@ -105,6 +106,15 @@
+ + warning +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts index 4211b2b09c..27de908e11 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts @@ -23,7 +23,8 @@ import { UntypedFormControl, UntypedFormGroup, ValidationErrors, - Validator, Validators, + Validator, + Validators, } from '@angular/forms'; import { ModbusMethodLabelsMap, @@ -31,8 +32,11 @@ import { ModbusOrderType, ModbusProtocolLabelsMap, ModbusProtocolType, + ModbusRegisterValues, + ModbusSlave, noLeadTrailSpacesRegex, - PortLimits, SlaveConfig, + PortLimits, + SlaveConfig, } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; @@ -104,7 +108,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], method: [ModbusMethodType.SOCKET, []], - unitId: [null, []], + unitId: [null, [Validators.required]], baudrate: [null, []], deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], @@ -155,13 +159,9 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat }; } - writeValue(slaveConfig: SlaveConfig): void { - if (slaveConfig.type === ModbusProtocolType.Serial) { - slaveConfig.serialPort = slaveConfig.port as string; - delete slaveConfig.port; - } - this.slaveConfigFormGroup.patchValue(slaveConfig, {emitEvent: false}); + writeValue(slaveConfig: ModbusSlave): void { this.showSecurityControl.patchValue(!!slaveConfig.security); + this.updateSlaveConfig(slaveConfig); this.updateControlsEnabling(slaveConfig.type); } @@ -180,4 +180,50 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat this.tcpUdpSpecificControlKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({emitEvent: false})); } }; + + private updateSlaveConfig(slaveConfig: ModbusSlave): void { + const { + type, + method, + unitId, + deviceName, + deviceType, + pollPeriod, + sendDataToThingsBoard, + byteOrder, + security, + identity, + values, + baudrate, + host, + port, + } = slaveConfig; + let slaveState: ModbusSlave = { + host: host ?? '', + type: type ?? ModbusProtocolType.TCP, + method: method ?? ModbusMethodType.SOCKET, + unitId: unitId ?? null, + deviceName: deviceName ?? '', + deviceType: deviceType ?? '', + pollPeriod: pollPeriod ?? null, + sendDataToThingsBoard: !!sendDataToThingsBoard, + byteOrder: byteOrder ?? ModbusOrderType.BIG, + security: security ?? {}, + identity: identity ?? { + vendorName: '', + productCode: '', + vendorUrl: '', + productName: '', + modelName: '', + }, + values: values ?? {} as ModbusRegisterValues, + port: port ?? null, + }; + if (slaveConfig.type === ModbusProtocolType.Serial) { + slaveState = { ...slaveState, baudrate, serialPort: port, host: '', port: null } as ModbusSlave; + } else { + slaveState = { ...slaveState, serialPort: '', baudrate: null } as ModbusSlave; + } + this.slaveConfigFormGroup.setValue(slaveState, {emitEvent: false}); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html index 95e7700886..3ac441447c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html @@ -176,6 +176,15 @@
+ + warning +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts index 991c71c16b..d989478e98 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts @@ -125,7 +125,7 @@ export class ModbusSlaveDialogComponent extends DialogComponent