Skip to content

Commit 3f233b1

Browse files
committed
Enable Display and Input in Hex format for modbus Addresses
1 parent 38d17b2 commit 3f233b1

File tree

9 files changed

+174
-12
lines changed

9 files changed

+174
-12
lines changed

cypress/hexinputfield.cy.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
import { HexinputfieldComponent } from "angular/src/app/specification/hexinputfield/hexinputfield.test.component";
3+
4+
function mount(startValue:number, displayHex:boolean) {
5+
cy.mount(HexinputfieldComponent, {
6+
autoDetectChanges: true,
7+
componentProperties: {
8+
displayHex: displayHex,
9+
startValue: startValue,
10+
},
11+
});
12+
}
13+
const inputField='input[formControlName="testHex"]'
14+
describe("Hexinputfield Component tests", () => {
15+
it("Show decimal", () => {
16+
mount(0x1234,false);
17+
cy.get(inputField).should('have.value', '4660');;
18+
cy.get(inputField).clear().type('1234').should('have.value', '1234');
19+
cy.get(inputField).clear().type('0x1234').blur().should('have.value', '4660');
20+
});
21+
it("Show Hex", () => {
22+
mount(0x1234,true);
23+
cy.get(inputField).should('have.value', '0x1234');;
24+
cy.get(inputField).clear().type('4660').blur().should('have.value', '0x1234');;
25+
cy.get(inputField).clear().type('0x1234').blur().should('have.value', '0x1234');;
26+
});
27+
});

cypress/specification-entity.cy.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ImodbusEntityWithName } from "angular/src/app/services/specificationInt
22
import {
33
afterEachEntityHelper,
44
beforeEachHelper as beforeEachEntityHelper,
5+
mountEntityComponent,
56
setOnEntityNameOrVariableFieldsChangeFunc,
67
} from "./support/entityHelper";
78

@@ -64,3 +65,16 @@ describe("Entity Component tests", () => {
6465
// "not.be.null");
6566
});
6667
});
68+
describe("Test for Modbus Address", () => {
69+
beforeEach(()=>{mountEntityComponent(true)}); // mounts entity and opens all expansion panels
70+
afterEach(afterEachEntityHelper);
71+
it("Modbus address in hex", () => {
72+
const inputField='input[formControlName="modbusAddress"]'
73+
const matField='mat-form-field input[formControlName="modbusAddress"]'
74+
cy.get(inputField).should('have.value', '0x4');
75+
cy.get(inputField).clear().type('1234').blur().should('have.value', '0x4d2');
76+
cy.get(inputField).clear().type('0X12s32').blur().should('have.value', '0x1232');
77+
cy.get(inputField).clear().type('0xx7')
78+
cy.get(inputField).parent().get( "mat-error").should('contain', 'dec or hex')
79+
})
80+
});

cypress/support/entityHelper.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ let selectEntity: ImodbusEntity = {
8888
*
8989
* If other initial values are required, a new test file is required
9090
*/
91-
export function beforeEachHelper() {
91+
export function mountEntityComponent(displayHex:boolean) {
9292
cy.intercept("GET", "**/converters", {
9393
fixture: "converters.json",
9494
});
@@ -105,9 +105,15 @@ export function beforeEachHelper() {
105105
specificationMethods: specificationMethods,
106106
entity: selectEntity,
107107
disabled: false,
108+
displayHex:displayHex
108109
},
109110
});
110111
cy.openAllExpansionPanels();
112+
113+
}
114+
115+
export function beforeEachHelper() {
116+
mountEntityComponent(false);
111117
}
112118
/**
113119
* resets the function set by setOnEntityNameOrVariableFieldsChangeFunc

src/app/specification/entity/entity.component.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,21 @@
7878
<div class="flexrows">
7979
<mat-form-field class="width100pt smallfield">
8080
<mat-label>Modbus Addr.</mat-label>
81-
<input #entityModbusAddress matInput type="number" formControlName="modbusAddress"
82-
[errorStateMatcher]="errorStateMatcher" (change)="onModbusAddressChange()">
81+
<input #entityModbusAddress matInput hexFormater [displayHex] ="displayHex" type="text" formControlName="modbusAddress"
82+
matTooltip="Please enter decimal (0-65536) or hexadecimal (0x123a) number"
83+
[errorStateMatcher]="errorStateMatcher" (change)="onModbusAddressChange()"
84+
(keypress)="onModbusAddressKeyPress($event)" >
8385
<mat-error *ngIf="entityFormGroup.get('modbusAddress')!.hasError('required')">
8486
This field is required
8587
</mat-error>
8688
<mat-error
8789
*ngIf="entityFormGroup.get('modbusAddress')!.hasError('min')|| entityFormGroup.get('modbusAddress')!.hasError('max')">
8890
Range is 1-65536
8991
</mat-error>
92+
<mat-error
93+
*ngIf="entityFormGroup.get('modbusAddress')!.hasError('pattern')">
94+
dec or hex
95+
</mat-error>
9096
</mat-form-field>
9197
<mat-form-field class="smallfield">
9298
<mat-label>Register</mat-label>

src/app/specification/entity/entity.component.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
} from "@angular/core";
99

1010
import { ApiService } from "../../services/api-service";
11+
import {HexFormaterDirective, HexFormaterPipe} from '../hexinputfield/hexinputfield'
12+
1113
import {
1214
AbstractControl,
1315
FormBuilder,
@@ -104,6 +106,7 @@ const newEntity: ImodbusEntityWithName = {
104106
styleUrl: "./entity.component.css",
105107
standalone: true,
106108
imports: [
109+
HexFormaterDirective,
107110
MatCard,
108111
MatCardHeader,
109112
MatCardTitle,
@@ -170,6 +173,8 @@ export class EntityComponent
170173
backupEntity: ImodbusEntityWithName | null;
171174
@Input()
172175
disabled: boolean = true;
176+
@Input()
177+
displayHex: boolean = false;
173178
entityCategories: string[] = ["", "config", "diagnostic"];
174179

175180
mqttValues: HTMLElement;
@@ -229,6 +234,7 @@ export class EntityComponent
229234
Validators.required,
230235
Validators.min(0),
231236
Validators.max(65536),
237+
Validators.pattern("^0[xX][0-9a-fA-F]+$|^[0-9]+$")
232238
]),
233239
],
234240
registerType: [null as IRegisterType | null, Validators.required],
@@ -442,7 +448,7 @@ export class EntityComponent
442448
this.entityFormGroup.get("registerType")!.setValue( { registerType: entity.registerType, name: "unknown"});
443449
converterFormControl.setValue(entity.converter);
444450
modbusAddressFormControl.setValue(
445-
entity.modbusAddress != undefined ? entity.modbusAddress : null,
451+
entity.modbusAddress != undefined ? HexFormaterDirective.convertNumberToInput(entity.modbusAddress,this.displayHex) : null,
446452
);
447453

448454
if (
@@ -454,7 +460,7 @@ export class EntityComponent
454460
);
455461
modbusAddressFormControl.setValue(
456462
entity.modbusAddress && entity.modbusAddress != -1
457-
? entity.modbusAddress
463+
? HexFormaterDirective.convertNumberToInput(entity.modbusAddress,this.displayHex)!
458464
: null,
459465
);
460466
}
@@ -664,6 +670,10 @@ export class EntityComponent
664670
this.specificationMethods.copy2Translation(this.entity);
665671
}
666672
}
673+
onModbusAddressKeyPress(event:KeyboardEvent){
674+
const allowed="0123456789xXABCDEFabcdef"
675+
return allowed.indexOf( event.key) >=0
676+
}
667677
onModbusAddressChange() {
668678
if (!this.entity) return;
669679
this.specificationMethods.setEntitiesTouched();
@@ -676,10 +686,8 @@ export class EntityComponent
676686
(converterFormControl.value != null &&
677687
converterFormControl.value !== this.entity.converter)
678688
) {
679-
this.entity.modbusAddress =
680-
modbusAddressFormControl.value != null
681-
? modbusAddressFormControl.value
682-
: undefined;
689+
if(modbusAddressFormControl.value != null)
690+
this.entity.modbusAddress = HexFormaterDirective.convertHexInput(modbusAddressFormControl.value)!
683691
if (converterFormControl.value !== null) {
684692
this.entity.converter = converterFormControl.value as Converters;
685693
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Component, Input, OnInit, } from '@angular/core';
2+
import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
3+
import {HexFormaterDirective} from './hexinputfield'
4+
@Component({
5+
selector: 'app-hexinputfield',
6+
imports: [
7+
FormsModule,
8+
ReactiveFormsModule,
9+
HexFormaterDirective
10+
],
11+
template: `
12+
<div [formGroup]="formGroup">
13+
<input hexFormater [displayHex] ="displayHex" type="text" formControlName="testHex">
14+
</div>
15+
`,
16+
styles: ``
17+
})
18+
export class HexinputfieldComponent implements OnInit{
19+
@Input() startValue:number = 0x1234;
20+
@Input() displayHex:boolean = false;
21+
formGroup:FormGroup;
22+
constructor( private fb: FormBuilder){}
23+
ngOnInit(): void {
24+
this.formGroup = this.fb.group({
25+
testHex: [HexFormaterDirective.convertNumberToInput(this.startValue,this.displayHex) ],
26+
});
27+
}
28+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Directive, HostListener, ElementRef, OnInit, Input } from '@angular/core';
2+
3+
import { Pipe, PipeTransform } from '@angular/core';
4+
5+
@Pipe({ name: 'hexFormaterPipe' })
6+
export class HexFormaterPipe implements PipeTransform {
7+
transform(value: number, hexFormat:boolean): string {
8+
if( hexFormat)
9+
return "0x" + value.toString(16);
10+
else
11+
return value.toString();
12+
}
13+
}
14+
15+
@Directive({
16+
selector: '[hexFormater]',
17+
})
18+
export class HexFormaterDirective implements OnInit {
19+
private el: HTMLInputElement;
20+
@Input() displayHex:boolean;
21+
constructor(
22+
private elementRef: ElementRef,
23+
) {
24+
this.el = this.elementRef.nativeElement;
25+
}
26+
27+
static convertHexInput(value:string):number| undefined{
28+
if(value=="")
29+
return undefined;
30+
var nv = (value.startsWith("0x")||value.startsWith("0X")? parseInt(value.substring(2),16):parseInt(value))
31+
if( Number.isNaN(nv))
32+
return undefined;
33+
return nv;
34+
}
35+
static convertNumberToInput(value:number| undefined, displayHex:boolean):string| undefined{
36+
if( value == undefined|| Number.isNaN(value))
37+
return undefined
38+
return (displayHex? "0x" + value.toString(16):value.toString())
39+
}
40+
41+
ngOnInit() {
42+
this.onFocus(this.el.value);
43+
}
44+
45+
@HostListener("focus", ["$event.target.value"])
46+
@HostListener("blur", ["$event.target.value"])
47+
onFocus(value:string) {
48+
var numValue = HexFormaterDirective.convertHexInput(value)
49+
if( numValue != undefined)
50+
this.el.value = HexFormaterDirective.convertNumberToInput(numValue,this.displayHex) as string
51+
}
52+
}

src/app/specification/specification/specification.component.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</div>
1111

1212
<div class="flexrowsWrapWhenSmall">
13+
1314
<mat-card class="card flex-align">
1415
<mat-card-header><mat-card-title><mat-icon
1516
[matTooltip]="getStatusText(currentSpecification?.status)">{{getStatusIcon(currentSpecification?.status)}}</mat-icon>{{getTranslatedSpecName()}}</mat-card-title>
@@ -80,11 +81,14 @@
8081
</mat-card-content>
8182
</mat-card>
8283
<div>
84+
<form id="displaySettings" [formGroup]="displayHexFormGroup" class="flexcolumns">
85+
<mat-slide-toggle (change)="onDisplayHexChanged($event)" formControlName="displayHex" >Display Modbus Addresses in Hex Format</mat-slide-toggle>
86+
</form>
8387
<div class="flexrows flex-auto">
8488
@for ( entity of currentSpecification?.entities; track entity.id){
85-
<app-entity [specificationMethods]="specificationMethods" [entity]="entity" [disabled]="disabled"></app-entity>
89+
<app-entity [specificationMethods]="specificationMethods" [entity]="entity" [disabled]="disabled" [displayHex]="displayHex"></app-entity>
8690
}
87-
<app-entity [specificationMethods]="specificationMethods" [disabled]="disabled"></app-entity>
91+
<app-entity [specificationMethods]="specificationMethods" [disabled]="disabled" [displayHex]="displayHex" ></app-entity>
8892
</div>
8993
</div>
9094
</div>

src/app/specification/specification/specification.component.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
Validators,
1818
FormsModule,
1919
ReactiveFormsModule,
20+
FormControl,
2021
} from "@angular/forms";
2122
import { ApiService } from "../../services/api-service";
2223
import {
@@ -73,9 +74,10 @@ import { UploadFilesComponent } from "../upload-files/upload-files.component";
7374
import {
7475
MatExpansionPanel,
7576
MatExpansionPanelHeader,
76-
MatExpansionPanelTitle,
77+
MatExpansionPanelTitle
7778
} from "@angular/material/expansion";
7879
import { MatList, MatListItem } from "@angular/material/list";
80+
import { MatSlideToggle, MatSlideToggleChange } from "@angular/material/slide-toggle";
7981
import {
8082
MatCard,
8183
MatCardHeader,
@@ -117,6 +119,7 @@ import { MatIconButton } from "@angular/material/button";
117119
MatInput,
118120
TranslationComponent,
119121
EntityComponent,
122+
MatSlideToggle
120123
],
121124
})
122125
export class SpecificationComponent
@@ -134,6 +137,8 @@ export class SpecificationComponent
134137
currentSpecification: ImodbusSpecification | null;
135138
originalSpecification: ImodbusSpecification | null;
136139
uploadModbusDocumentationFormGroup: FormGroup<any>;
140+
displayHexFormGroup: FormGroup<any>;
141+
displayHex:boolean;
137142
galleryConfig: GalleryConfig = { thumbs: false };
138143
private entitiesTouched: boolean = false;
139144
private saveSubject = new Subject<void>();
@@ -677,10 +682,17 @@ export class SpecificationComponent
677682
manufacturer: [null as string | null],
678683
model: [null as string | null],
679684
});
685+
this.displayHexFormGroup = this.fb.group({
686+
displayHex: [false]
687+
})
680688
this.validationForms = this.fb.group({ spec: this.enterSpecNameFormGroup });
681689

682690
this.entityApiService.getConfiguration().subscribe((config) => {
683691
this.config = config;
692+
var dispHexFg = this.displayHexFormGroup.get("displayHex");
693+
this.displayHex = this.config.displayHex?this.config.displayHex:false;
694+
if( dispHexFg )
695+
dispHexFg.setValue( this.config.displayHex)
684696
this.specServices = new SpecificationServices(
685697
this.config.mqttdiscoverylanguage,
686698
this.entityApiService,
@@ -842,4 +854,9 @@ export class SpecificationComponent
842854
getStatusText(status: SpecificationStatus | null): string {
843855
return SpecificationServices.getStatusText(status);
844856
}
857+
onDisplayHexChanged(event: MatSlideToggleChange){
858+
this.displayHex = event.checked
859+
this.config.displayHex = event.checked
860+
this.entityApiService.postConfiguration(this.config).subscribe(()=>{})
861+
}
845862
}

0 commit comments

Comments
 (0)