Skip to content

Commit ef93c73

Browse files
author
Dennis Labordus
committed
Locamation Plugin to update private fields, working screens.
Signed-off-by: Dennis Labordus <[email protected]>
1 parent aa2e1ac commit ef93c73

File tree

9 files changed

+3237
-100
lines changed

9 files changed

+3237
-100
lines changed

src/foundation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export function newActionEvent<T extends EditorAction>(
133133
}
134134

135135
export const wizardInputSelector =
136-
'wizard-textfield, mwc-textfield, ace-editor, mwc-select,wizard-select';
136+
'wizard-textfield, mwc-textfield, ace-editor, mwc-select, wizard-select';
137137
export type WizardInput =
138138
| WizardTextField
139139
| TextField

src/locamation/LocamationIEDList.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import {css, customElement, html, LitElement, property, TemplateResult} from 'lit-element';
22
import {get, translate} from "lit-translate";
33

4+
import '@material/mwc-list';
5+
import '@material/mwc-list/mwc-list-item';
6+
47
import {newSubWizardEvent, newWizardEvent, Wizard, WizardInput} from '../foundation.js';
58
import {isSCLNamespace} from "../schemas.js";
69
import {Nsdoc} from "../foundation/nsdoc.js";
7-
import {dispatchEventOnOpenScd} from "../compas/foundation.js";
810

911
import {iedHeader, lDeviceHeader} from "./foundation.js";
1012
import {locamationLNListWizard} from "./LocamationLNList.js";
@@ -17,15 +19,14 @@ export class LocamationIEDListElement extends LitElement {
1719
nsdoc!: Nsdoc;
1820

1921
private get logicaDevices(): Element[] {
20-
return Array.from(this.doc!.querySelectorAll(
21-
'IED[manufacturer="Locamation B.V."] LDevice'))
22+
return Array.from(this.doc!.querySelectorAll('IED[manufacturer="Locamation B.V."] LDevice'))
2223
.filter(isSCLNamespace)
2324
.filter(element => element.querySelector('LN > Private[type="LCMTN_VMU_SENSOR"]') !== null);
2425
}
2526

2627
close(): void {
2728
// Close the Save Dialog.
28-
dispatchEventOnOpenScd(newWizardEvent());
29+
this.dispatchEvent(newWizardEvent());
2930
}
3031

3132
render(): TemplateResult {

src/locamation/LocamationLNEdit.ts

Lines changed: 103 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
import {css, customElement, html, LitElement, property, TemplateResult} from 'lit-element';
22
import {get, translate} from "lit-translate";
33

4-
import {Wizard, WizardAction, WizardInput} from '../foundation.js';
4+
import {patterns} from "../wizards/foundation/limits.js";
5+
import {checkValidity, ComplexAction, Wizard, WizardAction, WizardInput, wizardInputSelector} from '../foundation.js';
56
import {Nsdoc} from "../foundation/nsdoc.js";
67

8+
import '../wizard-textfield.js';
9+
710
import {
11+
createEditorAction,
12+
getInputFieldValue,
13+
getPrivate,
814
getPrivateTextValue,
915
hasPrivateElement,
1016
iedHeader,
17+
inputFieldChanged,
1118
lDeviceHeader,
1219
lnHeader,
13-
LOCAMATION_PRIVATE
1420
} from "./foundation.js";
15-
import Protocol from "devtools-protocol";
16-
import integer = Protocol.integer;
1721

1822
@customElement('locamation-ln-edit')
1923
export class LocamationVMUEditElement extends LitElement {
@@ -22,102 +26,121 @@ export class LocamationVMUEditElement extends LitElement {
2226
@property()
2327
nsdoc!: Nsdoc;
2428

25-
save(inputs: WizardInput[]): WizardAction[] {
26-
// if (!this.fieldsChanged(inputs)) {
29+
save(): WizardAction[] {
30+
const inputs: WizardInput[] = Array.from(this.shadowRoot!.querySelectorAll(wizardInputSelector));
31+
const locamationPrivate = getPrivate(this.logicalNode);
32+
33+
if (!this.fieldsChanged(locamationPrivate, inputs) || !this.checkValidityInputs(inputs)) {
2734
return [];
28-
// }
29-
//
30-
// cloneElement(element, { name, desc });
31-
//
32-
// const oldPrivateElement = getPrivate(oldElement, 'compas_substation');
33-
// const newPrivateElement = getOrCreatePrivate(oldElement, newElement, 'compas_substation');
34-
//
35-
// const compasName = getInputFieldValue(inputs, 'compasName');
36-
// processPrivateTextElement(newPrivateElement, EXTENSION_NAMESPACE, 'compas', 'CompasName', compasName);
37-
//
38-
// if (oldPrivateElement) {
39-
// return [{old: {element: oldPrivateElement}, new: {element: newPrivateElement}}];
40-
// }
41-
// return [{new: {parent: newElement, element: newPrivateElement}}];
42-
}
43-
//
44-
// private fieldsChanged(inputs: WizardInput[]): boolean {
45-
// const oldIdentifier= getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'IDENTIFIER');
46-
// const oldChannel = getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'CHANNEL');
47-
// const oldTransformPrimary = getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'TRANSFORM-PRIMARY');
48-
// const oldTransformSecondary = getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'TRANSFORM-SECONDARY');
49-
// return inputFieldChanged(inputs, 'Identifier', oldIdentifier)
50-
// || inputFieldChanged(inputs, 'Channel', oldChannel)
51-
// || inputFieldChanged(inputs, 'TransformPrimary', oldTransformPrimary)
52-
// || inputFieldChanged(inputs, 'TransformSecondary', oldTransformSecondary);
53-
// }
54-
55-
private getIdentifierPart(partNr: integer): string {
56-
const identifier = getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'IDENTIFIER')?? '';
57-
const parts = identifier.trim().split('.');
58-
if (parts.length < partNr) {
59-
return '';
6035
}
61-
return parts[partNr];
36+
37+
const complexAction: ComplexAction = {
38+
actions: [],
39+
title: get('locamation.vmu.updateAction', {lnName: lnHeader(this.logicalNode, this.nsdoc)}),
40+
};
41+
42+
complexAction.actions.push(...createEditorAction(locamationPrivate, 'IDENTIFIER', getInputFieldValue(inputs, 'Identifier')));
43+
if (hasPrivateElement(this.logicalNode, 'SUM')) {
44+
complexAction.actions.push(...createEditorAction(locamationPrivate, 'SUM', getInputFieldValue(inputs, 'Sum')));
45+
} else {
46+
complexAction.actions.push(...createEditorAction(locamationPrivate, 'CHANNEL', getInputFieldValue(inputs, 'Channel')));
47+
}
48+
complexAction.actions.push(...createEditorAction(locamationPrivate, 'TRANSFORM-PRIMARY', getInputFieldValue(inputs, 'TransformPrimary')));
49+
complexAction.actions.push(...createEditorAction(locamationPrivate, 'TRANSFORM-SECONDARY', getInputFieldValue(inputs, 'TransformSecondary')));
50+
51+
return complexAction.actions.length ? [complexAction] : [];
52+
}
53+
54+
private fieldsChanged(locamationPrivate: Element, inputs: WizardInput[]): boolean {
55+
const oldIdentifier= getPrivateTextValue(locamationPrivate, 'IDENTIFIER');
56+
const oldChannel = getPrivateTextValue(locamationPrivate, 'CHANNEL');
57+
const oldSum = getPrivateTextValue(locamationPrivate, 'SUM');
58+
const oldTransformPrimary = getPrivateTextValue(locamationPrivate, 'TRANSFORM-PRIMARY');
59+
const oldTransformSecondary = getPrivateTextValue(locamationPrivate, 'TRANSFORM-SECONDARY');
60+
61+
return inputFieldChanged(inputs, 'Identifier', oldIdentifier)
62+
|| (hasPrivateElement(locamationPrivate, 'SUM') ? inputFieldChanged(inputs, 'Sum', oldSum) : false)
63+
|| (hasPrivateElement(locamationPrivate, 'CHANNEL') ? inputFieldChanged(inputs, 'Channel', oldChannel) : false)
64+
|| inputFieldChanged(inputs, 'TransformPrimary', oldTransformPrimary)
65+
|| inputFieldChanged(inputs, 'TransformSecondary', oldTransformSecondary);
66+
}
67+
68+
private checkValidityInputs(inputs: WizardInput[]): boolean {
69+
return Array.from(inputs).every(checkValidity);
6270
}
6371

6472
render(): TemplateResult {
6573
const lDevice = this.logicalNode.closest('LDevice')!;
6674
const ied = lDevice.closest('IED')!;
75+
const locamationPrivate = getPrivate(this.logicalNode);
76+
77+
// Depending on the value of the class the pattern for the CIM or VIM for SUM/CHANNEL will change.
78+
let channelPattern = '[0-5]';
79+
let sumPattern = '[0-5],[0-5],[0-5]';
80+
if (this.logicalNode.getAttribute('lnClass') === 'TVTR') {
81+
channelPattern = '[0-2]';
82+
sumPattern = '[0-2],[0-2],[0-2]';
83+
}
84+
6785
return html `
68-
<wizard-textfield label="IED"
86+
<wizard-textfield label="${translate('locamation.vmu.ied.name')}"
6987
.maybeValue=${iedHeader(ied)}
70-
helper="${translate('locamation.vmu.ied.name')}"
7188
disabled>
7289
</wizard-textfield>
73-
<wizard-textfield label="Logical Device"
90+
<wizard-textfield label="${translate('locamation.vmu.ldevice.name')}"
7491
.maybeValue=${lDeviceHeader(lDevice)}
75-
helper="${translate('locamation.vmu.ldevice.name')}"
7692
disabled>
7793
</wizard-textfield>
78-
<wizard-textfield label="Logical Node"
94+
<wizard-textfield label="${translate('locamation.vmu.ln.name')}"
7995
.maybeValue=${lnHeader(this.logicalNode, this.nsdoc)}
80-
helper="${translate('locamation.vmu.ln.name')}"
8196
disabled>
8297
</wizard-textfield>
8398
84-
<div id="Identifier">
85-
<wizard-textfield label="IdentifierPart0"
86-
.maybeValue=${this.getIdentifierPart(0)}
87-
helper="Identifier"
88-
required>
89-
</wizard-textfield>
90-
<wizard-textfield label="IdentifierPart1"
91-
.maybeValue=${this.getIdentifierPart(1)}
92-
helper="Identifier"
93-
required>
94-
</wizard-textfield>
95-
<wizard-textfield label="IdentifierPart2"
96-
.maybeValue=${this.getIdentifierPart(2)}
97-
helper="Identifier"
98-
required>
99-
</wizard-textfield>
100-
</div>
101-
${hasPrivateElement(this.logicalNode, LOCAMATION_PRIVATE, 'SUM') ?
102-
html `<wizard-textfield label="Sum"
103-
.maybeValue=${getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'SUM')}
104-
helper="Sum"
105-
required>
106-
</wizard-textfield>` :
107-
html `<wizard-textfield label="Channel"
108-
.maybeValue=${getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'CHANNEL')}
109-
helper="Channel"
110-
required>
111-
</wizard-textfield>`
99+
<wizard-textfield label="${translate('locamation.vmu.version')}"
100+
.maybeValue=${getPrivateTextValue(locamationPrivate, 'VERSION')}
101+
disabled>
102+
</wizard-textfield>
103+
104+
<wizard-textfield id="Identifier"
105+
label="Identifier"
106+
.maybeValue=${getPrivateTextValue(locamationPrivate, 'IDENTIFIER')}
107+
helper="${translate('locamation.vmu.identifierHelper')}"
108+
pattern="^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.(?!$)|$)){3}$"
109+
required
110+
dialogInitialFocus>
111+
</wizard-textfield>
112+
113+
${hasPrivateElement(locamationPrivate, 'SUM') ?
114+
html `<wizard-textfield id="Sum"
115+
label="Sum"
116+
.maybeValue=${getPrivateTextValue(locamationPrivate, 'SUM')}
117+
helper="The collection of three channel numbers for which the sum of currents or voltages will be calculated. The numbers are separated by commas. Values for the current sensor range from 0 - 5, for the voltage sensor 0-2."
118+
pattern="${sumPattern}"
119+
required>
120+
</wizard-textfield>` : html ``
112121
}
113-
<wizard-textfield label="TransformPrimary"
114-
.maybeValue=${getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'TRANSFORM-PRIMARY')}
115-
helper="TransformPrimary"
122+
${hasPrivateElement(locamationPrivate, 'CHANNEL') ?
123+
html `<wizard-textfield id="Channel"
124+
label="Channel"
125+
.maybeValue=${getPrivateTextValue(locamationPrivate, 'CHANNEL')}
126+
helper="The channel number on the sensor. Values for the current sensor range from 0 - 5, for the voltage sensor 0-2."
127+
pattern="${channelPattern}"
128+
required>
129+
</wizard-textfield>` : html ``
130+
}
131+
132+
<wizard-textfield id="TransformPrimary"
133+
label="Transform Primary"
134+
.maybeValue=${getPrivateTextValue(locamationPrivate, 'TRANSFORM-PRIMARY')}
135+
helper="The nominator of the ratio of the measement transformer."
136+
pattern="${patterns.unsigned}"
116137
required>
117138
</wizard-textfield>
118-
<wizard-textfield label="TransformSecondary"
119-
.maybeValue=${getPrivateTextValue(this.logicalNode, LOCAMATION_PRIVATE, 'TRANSFORM-SECONDARY')}
120-
helper="TransformSecondary"
139+
<wizard-textfield id="TransformSecondary"
140+
label="Transform Secondary"
141+
.maybeValue=${getPrivateTextValue(locamationPrivate, 'TRANSFORM-SECONDARY')}
142+
helper="The denominator of the ratio of the measement transformer."
143+
pattern="${patterns.unsigned}"
121144
required>
122145
</wizard-textfield>
123146
`;
@@ -139,7 +162,7 @@ export function locamationLNEditWizard(logicalNode: Element, nsdoc: Nsdoc): Wiza
139162
function save() {
140163
return function (inputs: WizardInput[], wizard: Element): WizardAction[] {
141164
const locamationVMUEditElement = <LocamationVMUEditElement>wizard.shadowRoot!.querySelector('locamation-ln-edit')
142-
return locamationVMUEditElement.save(inputs);
165+
return locamationVMUEditElement.save();
143166
};
144167
}
145168

src/locamation/LocamationLNList.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import {css, customElement, html, LitElement, property, TemplateResult} from 'lit-element';
22
import {get, translate} from "lit-translate";
33

4+
import '@material/mwc-list';
5+
import '@material/mwc-list/mwc-list-item';
6+
47
import {newSubWizardEvent, newWizardEvent, Wizard, WizardInput} from '../foundation.js';
58
import {isSCLNamespace} from "../schemas.js";
69
import {Nsdoc} from "../foundation/nsdoc.js";
710

8-
import {dispatchEventOnOpenScd} from "../compas/foundation.js";
11+
import '../wizard-textfield.js';
12+
913
import {locamationLNEditWizard} from "./LocamationLNEdit.js";
1014
import {iedHeader, lDeviceHeader, lnHeader} from "./foundation.js";
1115

@@ -24,7 +28,7 @@ export class LocamationLNodeListElement extends LitElement {
2428

2529
close(): void {
2630
// Close the Save Dialog.
27-
dispatchEventOnOpenScd(newWizardEvent());
31+
this.dispatchEvent(newWizardEvent());
2832
}
2933

3034
render(): TemplateResult {

src/locamation/foundation.ts

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import {Nsdoc} from "../foundation/nsdoc.js";
22

3-
import {getDescriptionAttribute, getInstanceAttribute, getNameAttribute, getValue, WizardInput} from "../foundation.js";
3+
import {
4+
cloneElement,
5+
getDescriptionAttribute,
6+
getInstanceAttribute,
7+
getNameAttribute,
8+
getValue,
9+
SimpleAction,
10+
WizardInput
11+
} from "../foundation.js";
412

513
export const LOCAMATION_PRIVATE = "LCMTN_VMU_SENSOR";
14+
export const LOCAMATION_NS = "https://www.locamation.com/61850/VMU/SCL";
15+
export const LOCAMATION_PREFIX = "lcmtn_ext";
16+
617

718
export function lnHeader(ln: Element, nsDoc: Nsdoc): string {
819
const prefix = ln.getAttribute('prefix');
@@ -27,28 +38,65 @@ export function iedHeader(ied: Element): string {
2738
return `${name}${description !== undefined ? ' (' + description + ')' : ''}`;
2839
}
2940

30-
export function getInputFieldValue(inputs: WizardInput[], labelName: string): string | null {
31-
return getValue(inputs.find(i => i.label === labelName)!);
41+
42+
export function getInputFieldValue(inputs: WizardInput[], id: string): string | null {
43+
return getValue(inputs.find(i => i.id === id)!);
3244
}
3345

34-
export function inputFieldChanged(inputs: WizardInput[], labelName: string, oldValue: string | null): boolean {
35-
const value = getInputFieldValue(inputs, labelName);
46+
export function inputFieldChanged(inputs: WizardInput[], id: string, oldValue: string | null): boolean {
47+
const value = getInputFieldValue(inputs, id);
3648
if (oldValue) {
3749
return value !== oldValue;
3850
}
3951
return value !== null;
4052
}
4153

42-
export function hasPrivateElement(element: Element | null, privateType: string, type: string): boolean {
43-
if (element) {
44-
return element.querySelector(`Private[type="${privateType}"] > P[type="${type}"]`) != null;
54+
55+
export function addPrefixAndNamespaceToDocument(element: Element): void {
56+
const rootElement = element.ownerDocument.firstElementChild!;
57+
if (!rootElement.hasAttribute('xmlns:' + LOCAMATION_PREFIX)) {
58+
rootElement.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' + LOCAMATION_PREFIX, LOCAMATION_NS);
59+
}
60+
}
61+
62+
export function getPrivate(element: Element): Element {
63+
return element.querySelector(`Private[type="${LOCAMATION_PRIVATE}"]`)!;
64+
}
65+
66+
export function createEditorAction(locamationPrivate: Element, fieldType: string, value: string | null): SimpleAction[] {
67+
let privateField = Array.from(locamationPrivate.querySelectorAll(`P[type="${fieldType}"]`))
68+
.filter(element => element.namespaceURI === LOCAMATION_NS)
69+
.pop();
70+
if (!privateField) {
71+
// Make sure the namespace is configured on the root element with the known prefix.
72+
addPrefixAndNamespaceToDocument(locamationPrivate);
73+
74+
privateField = locamationPrivate.ownerDocument.createElementNS(LOCAMATION_NS, "P");
75+
privateField.setAttribute("type", fieldType);
76+
privateField.textContent = value;
77+
return [{new: {parent: locamationPrivate, element: privateField}}];
78+
}
79+
80+
const newPrivateField = cloneElement(privateField, {});
81+
newPrivateField.textContent = value;
82+
return [{old: {element: privateField}, new: {element: newPrivateField}}];
83+
}
84+
85+
export function hasPrivateElement(locamationPrivate: Element, type: string): boolean {
86+
if (locamationPrivate) {
87+
return Array.from(locamationPrivate.querySelectorAll(`P[type="${type}"]`))
88+
.filter(element => element.namespaceURI === LOCAMATION_NS)
89+
.pop() !== undefined;
4590
}
4691
return false;
4792
}
4893

49-
export function getPrivateTextValue(element: Element | null, privateType: string, type: string): string | null {
50-
if (element) {
51-
const privateElement = element.querySelector(`Private[type="${privateType}"] > P[type="${type}"]`);
94+
export function getPrivateTextValue(locamationPrivate: Element, type: string): string | null {
95+
if (locamationPrivate) {
96+
const privateElement =
97+
Array.from(locamationPrivate.querySelectorAll(`P[type="${type}"]`))
98+
.filter(element => element.namespaceURI === LOCAMATION_NS)
99+
.pop();
52100
if (privateElement) {
53101
return privateElement.textContent;
54102
}

0 commit comments

Comments
 (0)