Skip to content

Commit b25f9a6

Browse files
author
Dennis Labordus
authored
feat(editor/laterbinding): Subscribe and unsubscribe from ExtRef for Later Binding (SMV) (openscd#944)
* Implemented subscribing and unsubscribing from ExtRef for Later Binding. * Review comments processed
1 parent d06d0b4 commit b25f9a6

19 files changed

+1053
-101
lines changed

public/js/plugins.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const officialPlugins = [
3838
name: 'Subscriber Later Binding (SMV)',
3939
src: '/src/editors/SMVSubscriberLaterBinding.js',
4040
icon: 'link',
41-
default: false,
41+
default: true,
4242
kind: 'editor',
4343
},
4444
{

src/editors/subscription/foundation.ts

Lines changed: 122 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { css, LitElement, query } from 'lit-element';
2-
import { compareNames, createElement } from '../../foundation.js';
2+
import {
3+
cloneElement,
4+
compareNames,
5+
createElement,
6+
getSclSchemaVersion,
7+
} from '../../foundation.js';
38
import { getFcdaReferences } from '../../foundation/ied.js';
49

510
export enum View {
@@ -96,26 +101,30 @@ const serviceTypes: Partial<Record<string, string>> = {
96101
};
97102

98103
/**
99-
* @param controlBlock - `ReportControl`, `GSEControl` or `SamepldValueControl` source element
100-
* @param fCDA - the source data. can be data attribute or data obejct (missing daName)
101-
* @returns ExtRef element
104+
* Create a new ExtRef Element depending on the SCL Edition copy attributes from the Control Element,
105+
* FCDA Element and related Elements.
106+
*
107+
* @param controlElement - `ReportControl`, `GSEControl` or `SampledValueControl` source element
108+
* @param fcdaElement - The source data attribute element.
109+
* @returns The new created ExtRef element, which can be added to the document.
102110
*/
103111
export function createExtRefElement(
104-
controlBlock: Element | undefined,
105-
fCDA: Element
112+
controlElement: Element | undefined,
113+
fcdaElement: Element
106114
): Element {
107-
const iedName = fCDA.closest('IED')?.getAttribute('name') ?? null;
115+
const iedName = fcdaElement.closest('IED')?.getAttribute('name') ?? null;
108116
const [ldInst, prefix, lnClass, lnInst, doName, daName] = [
109117
'ldInst',
110118
'prefix',
111119
'lnClass',
112120
'lnInst',
113121
'doName',
114122
'daName',
115-
].map(attr => fCDA.getAttribute(attr));
116-
if (fCDA.ownerDocument.documentElement.getAttribute('version') !== '2007')
117-
//Ed1 does not define serviceType and its MCD attribute starting with src...
118-
return createElement(fCDA.ownerDocument, 'ExtRef', {
123+
].map(attr => fcdaElement.getAttribute(attr));
124+
125+
if (getSclSchemaVersion(fcdaElement.ownerDocument) === '2003') {
126+
// Edition 2003(1) does not define serviceType and its MCD attribute starting with src...
127+
return createElement(fcdaElement.ownerDocument, 'ExtRef', {
119128
iedName,
120129
ldInst,
121130
lnClass,
@@ -124,10 +133,11 @@ export function createExtRefElement(
124133
doName,
125134
daName,
126135
});
136+
}
127137

128-
if (!controlBlock || !serviceTypes[controlBlock.tagName])
138+
if (!controlElement || !serviceTypes[controlElement.tagName]) {
129139
//for invalid control block tag name assume polling
130-
return createElement(fCDA.ownerDocument, 'ExtRef', {
140+
return createElement(fcdaElement.ownerDocument, 'ExtRef', {
131141
iedName,
132142
serviceType: 'Poll',
133143
ldInst,
@@ -137,19 +147,109 @@ export function createExtRefElement(
137147
doName,
138148
daName,
139149
});
150+
}
151+
152+
// default is empty string as attributes are mandatory acc to IEC 61850-6 >Ed2
153+
const srcLDInst =
154+
controlElement.closest('LDevice')?.getAttribute('inst') ?? '';
155+
const srcPrefix =
156+
controlElement.closest('LN0,LN')?.getAttribute('prefix') ?? '';
157+
const srcLNClass =
158+
controlElement.closest('LN0,LN')?.getAttribute('lnClass') ?? '';
159+
const srcLNInst = controlElement.closest('LN0,LN')?.getAttribute('inst');
160+
const srcCBName = controlElement.getAttribute('name') ?? '';
161+
162+
return createElement(fcdaElement.ownerDocument, 'ExtRef', {
163+
iedName,
164+
serviceType: serviceTypes[controlElement.tagName]!,
165+
ldInst,
166+
lnClass,
167+
lnInst,
168+
prefix,
169+
doName,
170+
daName,
171+
srcLDInst,
172+
srcPrefix,
173+
srcLNClass,
174+
srcLNInst: srcLNInst ? srcLNInst : null,
175+
srcCBName,
176+
});
177+
}
178+
179+
/**
180+
* Create a clone of the passed ExtRefElement and updated or set the required attributes on the cloned element
181+
* depending on the Edition and type of Control Element.
182+
*
183+
* @param extRefElement - The ExtRef Element to clone and update.
184+
* @param controlElement - `ReportControl`, `GSEControl` or `SampledValueControl` source element
185+
* @param fcdaElement - The source data attribute element.
186+
* @returns A cloned ExtRef Element with updated information to be used for example in a Replace Action.
187+
*/
188+
export function updateExtRefElement(
189+
extRefElement: Element,
190+
controlElement: Element | undefined,
191+
fcdaElement: Element
192+
): Element {
193+
const iedName = fcdaElement.closest('IED')?.getAttribute('name') ?? null;
194+
const [ldInst, prefix, lnClass, lnInst, doName, daName] = [
195+
'ldInst',
196+
'prefix',
197+
'lnClass',
198+
'lnInst',
199+
'doName',
200+
'daName',
201+
].map(attr => fcdaElement.getAttribute(attr));
202+
203+
if (getSclSchemaVersion(fcdaElement.ownerDocument) === '2003') {
204+
// Edition 2003(1) does not define serviceType and its MCD attribute starting with src...
205+
return cloneElement(extRefElement, {
206+
iedName,
207+
serviceType: null,
208+
ldInst,
209+
lnClass,
210+
lnInst,
211+
prefix,
212+
doName,
213+
daName,
214+
srcLDInst: null,
215+
srcPrefix: null,
216+
srcLNClass: null,
217+
srcLNInst: null,
218+
srcCBName: null,
219+
});
220+
}
221+
222+
if (!controlElement || !serviceTypes[controlElement.tagName]) {
223+
//for invalid control block tag name assume polling
224+
return cloneElement(extRefElement, {
225+
iedName,
226+
serviceType: 'Poll',
227+
ldInst,
228+
lnClass,
229+
lnInst,
230+
prefix,
231+
doName,
232+
daName,
233+
srcLDInst: null,
234+
srcPrefix: null,
235+
srcLNClass: null,
236+
srcLNInst: null,
237+
srcCBName: null,
238+
});
239+
}
140240

141-
// default is empty string as attributes are mendaroty acc to IEC 61850-6 >Ed2
142-
const srcLDInst = controlBlock.closest('LDevice')?.getAttribute('inst') ?? '';
241+
const srcLDInst =
242+
controlElement.closest('LDevice')?.getAttribute('inst') ?? '';
143243
const srcPrefix =
144-
controlBlock.closest('LN0,LN')?.getAttribute('prefix') ?? '';
244+
controlElement.closest('LN0,LN')?.getAttribute('prefix') ?? '';
145245
const srcLNClass =
146-
controlBlock.closest('LN0,LN')?.getAttribute('lnClass') ?? '';
147-
const srcLNInst = controlBlock.closest('LN0,LN')?.getAttribute('inst') ?? '';
148-
const srcCBName = controlBlock.getAttribute('name') ?? '';
246+
controlElement.closest('LN0,LN')?.getAttribute('lnClass') ?? '';
247+
const srcLNInst = controlElement.closest('LN0,LN')?.getAttribute('inst');
248+
const srcCBName = controlElement.getAttribute('name') ?? '';
149249

150-
return createElement(fCDA.ownerDocument, 'ExtRef', {
250+
return cloneElement(extRefElement, {
151251
iedName,
152-
serviceType: serviceTypes[controlBlock.tagName]!,
252+
serviceType: serviceTypes[controlElement.tagName]!,
153253
ldInst,
154254
lnClass,
155255
lnInst,
@@ -159,7 +259,7 @@ export function createExtRefElement(
159259
srcLDInst,
160260
srcPrefix,
161261
srcLNClass,
162-
srcLNInst,
262+
srcLNInst: srcLNInst ? srcLNInst : null,
163263
srcCBName,
164264
});
165265
}

src/editors/subscription/smv-laterbinding/ext-ref-laterbinding-list.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ import { nothing } from 'lit-html';
1111
import { translate } from 'lit-translate';
1212

1313
import {
14+
cloneElement,
1415
compareNames,
1516
getDescriptionAttribute,
1617
getNameAttribute,
1718
identity,
19+
newActionEvent,
20+
Replace,
1821
} from '../../../foundation.js';
1922

20-
import { styles } from '../foundation.js';
23+
import { styles, updateExtRefElement } from '../foundation.js';
2124
import { FcdaSelectEvent, getFcdaTitleValue } from './foundation.js';
2225

2326
/**
@@ -147,6 +150,60 @@ export class ExtRefLaterBindingList extends LitElement {
147150
);
148151
}
149152

153+
/**
154+
* Unsubscribing means removing a list of attributes from the ExtRef Element.
155+
*
156+
* @param extRefElement - The Ext Ref Element to clean from attributes.
157+
*/
158+
private unsubscribe(extRefElement: Element): Replace {
159+
const clonedExtRefElement = cloneElement(extRefElement, {
160+
iedName: null,
161+
ldInst: null,
162+
prefix: null,
163+
lnClass: null,
164+
lnInst: null,
165+
doName: null,
166+
daName: null,
167+
serviceType: null,
168+
srcLDInst: null,
169+
srcPrefix: null,
170+
srcLNClass: null,
171+
srcLNInst: null,
172+
srcCBName: null,
173+
});
174+
175+
return {
176+
old: { element: extRefElement },
177+
new: { element: clonedExtRefElement },
178+
};
179+
}
180+
181+
/**
182+
* Subscribing means copying a list of attributes from the FCDA Element (and others) to the ExtRef Element.
183+
*
184+
* @param extRefElement - The Ext Ref Element to add the attributes to.
185+
*/
186+
private subscribe(extRefElement: Element): Replace | null {
187+
if (
188+
!this.currentIedElement ||
189+
!this.currentSelectedFcdaElement ||
190+
!this.currentSelectedSvcElement!
191+
) {
192+
return null;
193+
}
194+
195+
return {
196+
old: { element: extRefElement },
197+
new: {
198+
element: updateExtRefElement(
199+
extRefElement,
200+
this.currentSelectedSvcElement,
201+
this.currentSelectedFcdaElement
202+
),
203+
},
204+
};
205+
}
206+
150207
private renderTitle(): TemplateResult {
151208
const svcName = this.currentSelectedSvcElement
152209
? getNameAttribute(this.currentSelectedSvcElement)
@@ -185,6 +242,11 @@ export class ExtRefLaterBindingList extends LitElement {
185242
extRefElement => html` <mwc-list-item
186243
graphic="large"
187244
twoline
245+
@click=${() => {
246+
this.dispatchEvent(
247+
newActionEvent(this.unsubscribe(extRefElement))
248+
);
249+
}}
188250
value="${identity(extRefElement)}"
189251
>
190252
<span>
@@ -230,6 +292,12 @@ export class ExtRefLaterBindingList extends LitElement {
230292
graphic="large"
231293
?disabled=${this.unsupportedExtRefElement(extRefElement)}
232294
twoline
295+
@click=${() => {
296+
const replaceAction = this.subscribe(extRefElement);
297+
if (replaceAction) {
298+
this.dispatchEvent(newActionEvent(replaceAction));
299+
}
300+
}}
233301
value="${identity(extRefElement)}"
234302
>
235303
<span>

src/foundation.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,16 @@ export function referencePath(element: Element): string {
486486
return path;
487487
}
488488

489+
export type SclEdition = '2003' | '2007B' | '2007B4';
490+
export function getSclSchemaVersion(doc: Document): SclEdition {
491+
const scl: Element = doc.documentElement;
492+
const edition =
493+
(scl.getAttribute('version') ?? '2003') +
494+
(scl.getAttribute('revision') ?? '') +
495+
(scl.getAttribute('release') ?? '');
496+
return <SclEdition>edition;
497+
}
498+
489499
/**
490500
* Extract the 'name' attribute from the given XML element.
491501
* @param element - The element to extract name from.

test/integration/__snapshots__/open-scd.test.snap.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
/* @web/test-runner snapshot v1 */
22
export const snapshots = {};
33

4-
snapshots["open-scd looks like its snapshot"] =
5-
`<mwc-drawer
4+
snapshots['open-scd looks like its snapshot'] = `<mwc-drawer
65
class="mdc-theme--surface"
76
hasheader=""
87
id="menu"
@@ -659,6 +658,7 @@ snapshots["open-scd looks like its snapshot"] =
659658
hasmeta=""
660659
left=""
661660
mwc-list-item=""
661+
selected=""
662662
tabindex="-1"
663663
value="/src/editors/SMVSubscriberLaterBinding.js"
664664
>
@@ -1364,4 +1364,3 @@ snapshots["open-scd looks like its snapshot"] =
13641364
</mwc-dialog>
13651365
`;
13661366
/* end snapshot open-scd looks like its snapshot */
1367-
Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { expect, fixture, html } from '@open-wc/testing';
22

3-
import '../../../../mock-wizard.js';
3+
import '../../mock-wizard.js';
44

55
import { ListItem } from '@material/mwc-list/mwc-list-item.js';
66

7-
import { Editing } from '../../../../../src/Editing.js';
8-
import { Wizarding } from '../../../../../src/Wizarding.js';
9-
import GooseControlSubscriptionPlugin from '../../../../../src/editors/GooseControlSubscription.js';
7+
import { Editing } from '../../../src/Editing.js';
8+
import { Wizarding } from '../../../src/Wizarding.js';
9+
import GooseControlSubscriptionPlugin from '../../../src/editors/GooseControlSubscription.js';
1010

1111
describe('GOOSE subscriber plugin', () => {
1212
customElements.define(
@@ -139,11 +139,9 @@ describe('GOOSE subscriber plugin', () => {
139139
?.getAttribute('inst')}"][srcPrefix="${
140140
fcda.closest('LN0')?.getAttribute('prefix') ?? '' //prefix is mendatory in ExtRef!!
141141
}"][srcLNClass="${fcda
142-
.closest('LN0')
143-
?.getAttribute('lnClass')}"][srcLNInst="${fcda
144142
.closest('LN0')
145143
?.getAttribute(
146-
'inst'
144+
'lnClass'
147145
)}"][srcCBName="${gseControlBlock.getAttribute(
148146
'name'
149147
)}"][serviceType="GOOSE"]`
@@ -334,11 +332,9 @@ describe('GOOSE subscriber plugin', () => {
334332
?.getAttribute('inst')}"][srcPrefix="${
335333
fcda.closest('LN0')?.getAttribute('prefix') ?? '' //prefix is mendatory in ExtRef!!
336334
}"][srcLNClass="${fcda
337-
.closest('LN0')
338-
?.getAttribute('lnClass')}"][srcLNInst="${fcda
339335
.closest('LN0')
340336
?.getAttribute(
341-
'inst'
337+
'lnClass'
342338
)}"][srcCBName="${gseControlBlock.getAttribute(
343339
'name'
344340
)}"][serviceType="GOOSE"]`

0 commit comments

Comments
 (0)