Added posibility to configure modifier type for timeseries and attributes of Modbus Connector slaves

This commit is contained in:
mpetrov 2024-09-16 17:36:11 +03:00
parent 05a813a2c8
commit bbbc6fd558
4 changed files with 159 additions and 5 deletions

View File

@ -148,6 +148,62 @@
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div *ngIf="showModifiersMap.get(keyControl.get('id').value)" class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="enableModifiersControlMap.get(keyControl.get('id').value).value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle
fxLayoutAlign="center"
[formControl]="enableModifiersControlMap.get(keyControl.get('id').value)"
class="mat-slide"
(click)="$event.stopPropagation()"
>
<mat-label tb-hint-tooltip-icon="{{ 'gateway.hints.modbus.modifier' | translate }}">
{{ 'gateway.modifier' | translate }}
</mat-label>
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="tb-flex no-gap">
<div class="tb-form-row column-xs tb-flex full-width" fxLayoutAlign="space-between center">
<div class="fixed-title-width" translate>gateway.type</div>
<div class="tb-flex no-gap">
<mat-form-field class="tb-flex no-gap fill-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="modifierType">
<mat-select-trigger>
<div class="tb-flex align-center">
<mat-icon class="tb-mat-18" [svgIcon]="ModifierTypesMap.get(keyControl.get('modifierType').value)?.icon"></mat-icon>
<span>{{ ModifierTypesMap.get(keyControl.get('modifierType').value)?.name | translate}}</span>
</div>
</mat-select-trigger>
<mat-option *ngFor="let modifierType of modifierTypes" [value]="modifierType">
<mat-icon class="tb-mat-20" svgIcon="{{ ModifierTypesMap.get(modifierType).icon }}">
</mat-icon>
<span>{{ ModifierTypesMap.get(modifierType).name | translate }}</span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width" translate>gateway.value</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic" class="tb-inline-field flex tb-suffix-absolute">
<input matInput required formControlName="modifierValue" step="0.1" type="number"
placeholder="{{ 'gateway.set' | translate }}" />
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="('gateway.modifier-invalid') | translate"
*ngIf="keyControl.get('modifierValue').hasError('pattern') &&
keyControl.get('modifierValue').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
</mat-expansion-panel>
</div>
<div *ngIf="isMaster" class="tb-form-row column-xs" fxLayoutAlign="space-between center"> <div *ngIf="isMaster" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
<div class="fixed-title-width tb-required" translate>gateway.value</div> <div class="fixed-title-width tb-required" translate>gateway.value</div>
<div class="tb-flex no-gap"> <div class="tb-flex no-gap">

View File

@ -18,6 +18,7 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angu
import { import {
AbstractControl, AbstractControl,
FormArray, FormArray,
FormControl,
FormGroup, FormGroup,
UntypedFormArray, UntypedFormArray,
UntypedFormBuilder, UntypedFormBuilder,
@ -32,7 +33,10 @@ import {
ModbusObjectCountByDataType, ModbusObjectCountByDataType,
ModbusValue, ModbusValue,
ModbusValueKey, ModbusValueKey,
ModifierType,
ModifierTypesMap,
noLeadTrailSpacesRegex, noLeadTrailSpacesRegex,
nonZeroFloat,
} from '@home/components/widget/lib/gateway/gateway-widget.models'; } from '@home/components/widget/lib/gateway/gateway-widget.models';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
@ -69,12 +73,17 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
keysListFormArray: FormArray<UntypedFormGroup>; keysListFormArray: FormArray<UntypedFormGroup>;
modbusDataTypes = Object.values(ModbusDataType); modbusDataTypes = Object.values(ModbusDataType);
modifierTypes: ModifierType[] = Object.values(ModifierType);
withFunctionCode = true; withFunctionCode = true;
enableModifiersControlMap = new Map<string, FormControl<boolean>>();
showModifiersMap = new Map<string, boolean>();
functionCodesMap = new Map(); functionCodesMap = new Map();
defaultFunctionCodes = []; defaultFunctionCodes = [];
readonly ModbusEditableDataTypes = ModbusEditableDataTypes; readonly ModbusEditableDataTypes = ModbusEditableDataTypes;
readonly ModbusFunctionCodeTranslationsMap = ModbusFunctionCodeTranslationsMap; readonly ModbusFunctionCodeTranslationsMap = ModbusFunctionCodeTranslationsMap;
readonly ModifierTypesMap = ModifierTypesMap;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
@ -101,6 +110,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
} }
addKey(): void { addKey(): void {
const id = generateSecret(5);
const dataKeyFormGroup = this.fb.group({ const dataKeyFormGroup = this.fb.group({
tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
value: [{value: '', disabled: !this.isMaster}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], value: [{value: '', disabled: !this.isMaster}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
@ -108,9 +118,14 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
address: [null, [Validators.required]], address: [null, [Validators.required]],
objectsCount: [1, [Validators.required]], objectsCount: [1, [Validators.required]],
functionCode: [{ value: this.getDefaultFunctionCodes()[0], disabled: !this.withFunctionCode }, [Validators.required]], functionCode: [{ value: this.getDefaultFunctionCodes()[0], disabled: !this.withFunctionCode }, [Validators.required]],
id: [{value: generateSecret(5), disabled: true}], modifierType: [{ value: ModifierType.MULTIPLIER, disabled: true }],
modifierValue: [{ value: 1, disabled: true }, [Validators.pattern(nonZeroFloat)]],
id: [{value: id, disabled: true}],
}); });
this.showModifiersMap.set(id, false);
this.enableModifiersControlMap.set(id, this.fb.control(false));
this.observeKeyDataType(dataKeyFormGroup); this.observeKeyDataType(dataKeyFormGroup);
this.observeEnableModifier(dataKeyFormGroup);
this.keysListFormArray.push(dataKeyFormGroup); this.keysListFormArray.push(dataKeyFormGroup);
} }
@ -128,7 +143,17 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
} }
applyKeysData(): void { applyKeysData(): void {
this.keysDataApplied.emit(this.keysListFormArray.value); this.keysDataApplied.emit(this.mapKeysWithModifier());
}
private mapKeysWithModifier(): Array<ModbusValue> {
return this.keysListFormArray.value.map((keyData, i) => {
if (this.showModifiersMap.get(this.keysListFormArray.controls[i].get('id').value)) {
const { modifierType, modifierValue, ...value } = keyData;
return modifierType ? { ...value, [modifierType]: modifierValue } : value;
}
return keyData;
});
} }
private prepareKeysFormArray(values: ModbusValue[]): UntypedFormArray { private prepareKeysFormArray(values: ModbusValue[]): UntypedFormArray {
@ -138,6 +163,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
values.forEach(value => { values.forEach(value => {
const dataKeyFormGroup = this.createDataKeyFormGroup(value); const dataKeyFormGroup = this.createDataKeyFormGroup(value);
this.observeKeyDataType(dataKeyFormGroup); this.observeKeyDataType(dataKeyFormGroup);
this.observeEnableModifier(dataKeyFormGroup);
this.functionCodesMap.set(dataKeyFormGroup.get('id').value, this.getFunctionCodes(value.type)); this.functionCodesMap.set(dataKeyFormGroup.get('id').value, this.getFunctionCodes(value.type));
keysControlGroups.push(dataKeyFormGroup); keysControlGroups.push(dataKeyFormGroup);
@ -148,7 +174,12 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
} }
private createDataKeyFormGroup(modbusValue: ModbusValue): FormGroup { private createDataKeyFormGroup(modbusValue: ModbusValue): FormGroup {
const { tag, value, type, address, objectsCount, functionCode } = modbusValue; const { tag, value, type, address, objectsCount, functionCode, multiplier, divider } = modbusValue;
const id = generateSecret(5);
const showModifier = this.shouldShowModifier(type);
this.showModifiersMap.set(id, showModifier);
this.enableModifiersControlMap.set(id, this.fb.control((multiplier || divider) && showModifier));
return this.fb.group({ return this.fb.group({
tag: [tag, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], tag: [tag, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]],
@ -157,19 +188,54 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {
address: [address, [Validators.required]], address: [address, [Validators.required]],
objectsCount: [objectsCount, [Validators.required]], objectsCount: [objectsCount, [Validators.required]],
functionCode: [{ value: functionCode, disabled: !this.withFunctionCode }, [Validators.required]], functionCode: [{ value: functionCode, disabled: !this.withFunctionCode }, [Validators.required]],
id: [{ value: generateSecret(5), disabled: true }], modifierType: [{
value: divider ? ModifierType.DIVIDER : ModifierType.MULTIPLIER,
disabled: !this.enableModifiersControlMap.get(id).value
}],
modifierValue: [
{ value: multiplier ?? divider ?? 1, disabled: !this.enableModifiersControlMap.get(id).value },
[Validators.pattern(nonZeroFloat)]
],
id: [{ value: id, disabled: true }],
}); });
} }
private shouldShowModifier(type: ModbusDataType): boolean {
return !this.isMaster
&& (this.keysType === ModbusValueKey.ATTRIBUTES || this.keysType === ModbusValueKey.TIMESERIES)
&& (!this.ModbusEditableDataTypes.includes(type));
}
private observeKeyDataType(keyFormGroup: FormGroup): void { private observeKeyDataType(keyFormGroup: FormGroup): void {
keyFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(dataType => { keyFormGroup.get('type').valueChanges.pipe(takeUntil(this.destroy$)).subscribe(dataType => {
if (!this.ModbusEditableDataTypes.includes(dataType)) { if (!this.ModbusEditableDataTypes.includes(dataType)) {
keyFormGroup.get('objectsCount').patchValue(ModbusObjectCountByDataType[dataType], {emitEvent: false}); keyFormGroup.get('objectsCount').patchValue(ModbusObjectCountByDataType[dataType], {emitEvent: false});
} }
const withModifier = this.shouldShowModifier(dataType);
this.showModifiersMap.set(keyFormGroup.get('id').value, withModifier);
this.updateFunctionCodes(keyFormGroup, dataType); this.updateFunctionCodes(keyFormGroup, dataType);
}); });
} }
private observeEnableModifier(keyFormGroup: FormGroup): void {
this.enableModifiersControlMap.get(keyFormGroup.get('id').value).valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(showModifier => this.toggleModifierControls(keyFormGroup, showModifier));
}
private toggleModifierControls(keyFormGroup: FormGroup, enable: boolean): void {
const modifierTypeControl = keyFormGroup.get('modifierType');
const modifierValueControl = keyFormGroup.get('modifierValue');
if (enable) {
modifierTypeControl.enable();
modifierValueControl.enable();
} else {
modifierTypeControl.disable();
modifierValueControl.disable();
}
}
private updateFunctionCodes(keyFormGroup: FormGroup, dataType: ModbusDataType): void { private updateFunctionCodes(keyFormGroup: FormGroup, dataType: ModbusDataType): void {
const functionCodes = this.getFunctionCodes(dataType); const functionCodes = this.getFunctionCodes(dataType);
this.functionCodesMap.set(keyFormGroup.get('id').value, functionCodes); this.functionCodesMap.set(keyFormGroup.get('id').value, functionCodes);

View File

@ -19,6 +19,7 @@ import { AttributeData } from '@shared/models/telemetry/telemetry.models';
export const noLeadTrailSpacesRegex = /^\S+(?: \S+)*$/; export const noLeadTrailSpacesRegex = /^\S+(?: \S+)*$/;
export const integerRegex = /^[-+]?\d+$/; export const integerRegex = /^[-+]?\d+$/;
export const nonZeroFloat = /^-?(?!0(\.0+)?$)\d+(\.\d+)?$/;
export enum StorageTypes { export enum StorageTypes {
MEMORY = 'memory', MEMORY = 'memory',
@ -897,6 +898,30 @@ export enum MappingValueType {
BOOLEAN = 'boolean' BOOLEAN = 'boolean'
} }
export enum ModifierType {
DIVIDER = 'divider',
MULTIPLIER = 'multiplier',
}
export const ModifierTypesMap = new Map<ModifierType, ValueTypeData>(
[
[
ModifierType.DIVIDER,
{
name: 'gateway.divider',
icon: 'mdi:division'
}
],
[
ModifierType.MULTIPLIER,
{
name: 'gateway.multiplier',
icon: 'mdi:multiplication'
}
],
]
);
export const mappingValueTypesMap = new Map<MappingValueType, ValueTypeData>( export const mappingValueTypesMap = new Map<MappingValueType, ValueTypeData>(
[ [
[ [
@ -1141,6 +1166,8 @@ export interface ModbusValue {
objectsCount: number; objectsCount: number;
address: number; address: number;
value?: string; value?: string;
multiplier?: number;
divider?: number;
} }
export interface ModbusSecurity { export interface ModbusSecurity {

View File

@ -2934,6 +2934,7 @@
"details": "Details", "details": "Details",
"delete-mapping-title": "Delete mapping?", "delete-mapping-title": "Delete mapping?",
"delete-slave-title": "Delete slave?", "delete-slave-title": "Delete slave?",
"divider": "Divider",
"download-configuration-file": "Download configuration file", "download-configuration-file": "Download configuration file",
"download-docker-compose": "Download docker-compose.yml for your gateway", "download-docker-compose": "Download docker-compose.yml for your gateway",
"enable-remote-logging": "Enable remote logging", "enable-remote-logging": "Enable remote logging",
@ -3089,8 +3090,11 @@
"min-pack-send-delay-required": "Min pack send delay is required", "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 10", "min-pack-send-delay-min": "Min pack send delay can not be less then 10",
"min-pack-send-delay-pattern": "Min pack send delay is not valid", "min-pack-send-delay-pattern": "Min pack send delay is not valid",
"multiplier": "Multiplier",
"mode": "Mode", "mode": "Mode",
"model-name": "Model name", "model-name": "Model name",
"modifier": "Modifier",
"modifier-invalid": "Modifier is not valid",
"mqtt-version": "MQTT version", "mqtt-version": "MQTT version",
"name": "Name", "name": "Name",
"name-required": "Name is required.", "name-required": "Name is required.",
@ -3493,7 +3497,8 @@
"objects-count": "Depends on the selected type.", "objects-count": "Depends on the selected type.",
"address": "Register address to verify.", "address": "Register address to verify.",
"key": "Key to be used as the attribute key for the platform instance.", "key": "Key to be used as the attribute key for the platform instance.",
"data-keys": "For more information about function codes and data types click on help icon" "data-keys": "For more information about function codes and data types click on help icon",
"modifier": "The retrieved value will be adjusted (by multiplying or dividing it) based on the specified modifier value."
} }
} }
}, },