Skip to content

Commit

Permalink
feat(editor/laterbinding): Subscribe and unsubscribe from ExtRef for …
Browse files Browse the repository at this point in the history
…Later Binding (SMV) (openscd#944)

* Implemented subscribing and unsubscribing from ExtRef for Later Binding.
* Review comments processed
  • Loading branch information
Dennis Labordus authored Aug 18, 2022
1 parent d06d0b4 commit b25f9a6
Show file tree
Hide file tree
Showing 19 changed files with 1,053 additions and 101 deletions.
2 changes: 1 addition & 1 deletion public/js/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const officialPlugins = [
name: 'Subscriber Later Binding (SMV)',
src: '/src/editors/SMVSubscriberLaterBinding.js',
icon: 'link',
default: false,
default: true,
kind: 'editor',
},
{
Expand Down
144 changes: 122 additions & 22 deletions src/editors/subscription/foundation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { css, LitElement, query } from 'lit-element';
import { compareNames, createElement } from '../../foundation.js';
import {
cloneElement,
compareNames,
createElement,
getSclSchemaVersion,
} from '../../foundation.js';
import { getFcdaReferences } from '../../foundation/ied.js';

export enum View {
Expand Down Expand Up @@ -96,26 +101,30 @@ const serviceTypes: Partial<Record<string, string>> = {
};

/**
* @param controlBlock - `ReportControl`, `GSEControl` or `SamepldValueControl` source element
* @param fCDA - the source data. can be data attribute or data obejct (missing daName)
* @returns ExtRef element
* Create a new ExtRef Element depending on the SCL Edition copy attributes from the Control Element,
* FCDA Element and related Elements.
*
* @param controlElement - `ReportControl`, `GSEControl` or `SampledValueControl` source element
* @param fcdaElement - The source data attribute element.
* @returns The new created ExtRef element, which can be added to the document.
*/
export function createExtRefElement(
controlBlock: Element | undefined,
fCDA: Element
controlElement: Element | undefined,
fcdaElement: Element
): Element {
const iedName = fCDA.closest('IED')?.getAttribute('name') ?? null;
const iedName = fcdaElement.closest('IED')?.getAttribute('name') ?? null;
const [ldInst, prefix, lnClass, lnInst, doName, daName] = [
'ldInst',
'prefix',
'lnClass',
'lnInst',
'doName',
'daName',
].map(attr => fCDA.getAttribute(attr));
if (fCDA.ownerDocument.documentElement.getAttribute('version') !== '2007')
//Ed1 does not define serviceType and its MCD attribute starting with src...
return createElement(fCDA.ownerDocument, 'ExtRef', {
].map(attr => fcdaElement.getAttribute(attr));

if (getSclSchemaVersion(fcdaElement.ownerDocument) === '2003') {
// Edition 2003(1) does not define serviceType and its MCD attribute starting with src...
return createElement(fcdaElement.ownerDocument, 'ExtRef', {
iedName,
ldInst,
lnClass,
Expand All @@ -124,10 +133,11 @@ export function createExtRefElement(
doName,
daName,
});
}

if (!controlBlock || !serviceTypes[controlBlock.tagName])
if (!controlElement || !serviceTypes[controlElement.tagName]) {
//for invalid control block tag name assume polling
return createElement(fCDA.ownerDocument, 'ExtRef', {
return createElement(fcdaElement.ownerDocument, 'ExtRef', {
iedName,
serviceType: 'Poll',
ldInst,
Expand All @@ -137,19 +147,109 @@ export function createExtRefElement(
doName,
daName,
});
}

// default is empty string as attributes are mandatory acc to IEC 61850-6 >Ed2
const srcLDInst =
controlElement.closest('LDevice')?.getAttribute('inst') ?? '';
const srcPrefix =
controlElement.closest('LN0,LN')?.getAttribute('prefix') ?? '';
const srcLNClass =
controlElement.closest('LN0,LN')?.getAttribute('lnClass') ?? '';
const srcLNInst = controlElement.closest('LN0,LN')?.getAttribute('inst');
const srcCBName = controlElement.getAttribute('name') ?? '';

return createElement(fcdaElement.ownerDocument, 'ExtRef', {
iedName,
serviceType: serviceTypes[controlElement.tagName]!,
ldInst,
lnClass,
lnInst,
prefix,
doName,
daName,
srcLDInst,
srcPrefix,
srcLNClass,
srcLNInst: srcLNInst ? srcLNInst : null,
srcCBName,
});
}

/**
* Create a clone of the passed ExtRefElement and updated or set the required attributes on the cloned element
* depending on the Edition and type of Control Element.
*
* @param extRefElement - The ExtRef Element to clone and update.
* @param controlElement - `ReportControl`, `GSEControl` or `SampledValueControl` source element
* @param fcdaElement - The source data attribute element.
* @returns A cloned ExtRef Element with updated information to be used for example in a Replace Action.
*/
export function updateExtRefElement(
extRefElement: Element,
controlElement: Element | undefined,
fcdaElement: Element
): Element {
const iedName = fcdaElement.closest('IED')?.getAttribute('name') ?? null;
const [ldInst, prefix, lnClass, lnInst, doName, daName] = [
'ldInst',
'prefix',
'lnClass',
'lnInst',
'doName',
'daName',
].map(attr => fcdaElement.getAttribute(attr));

if (getSclSchemaVersion(fcdaElement.ownerDocument) === '2003') {
// Edition 2003(1) does not define serviceType and its MCD attribute starting with src...
return cloneElement(extRefElement, {
iedName,
serviceType: null,
ldInst,
lnClass,
lnInst,
prefix,
doName,
daName,
srcLDInst: null,
srcPrefix: null,
srcLNClass: null,
srcLNInst: null,
srcCBName: null,
});
}

if (!controlElement || !serviceTypes[controlElement.tagName]) {
//for invalid control block tag name assume polling
return cloneElement(extRefElement, {
iedName,
serviceType: 'Poll',
ldInst,
lnClass,
lnInst,
prefix,
doName,
daName,
srcLDInst: null,
srcPrefix: null,
srcLNClass: null,
srcLNInst: null,
srcCBName: null,
});
}

// default is empty string as attributes are mendaroty acc to IEC 61850-6 >Ed2
const srcLDInst = controlBlock.closest('LDevice')?.getAttribute('inst') ?? '';
const srcLDInst =
controlElement.closest('LDevice')?.getAttribute('inst') ?? '';
const srcPrefix =
controlBlock.closest('LN0,LN')?.getAttribute('prefix') ?? '';
controlElement.closest('LN0,LN')?.getAttribute('prefix') ?? '';
const srcLNClass =
controlBlock.closest('LN0,LN')?.getAttribute('lnClass') ?? '';
const srcLNInst = controlBlock.closest('LN0,LN')?.getAttribute('inst') ?? '';
const srcCBName = controlBlock.getAttribute('name') ?? '';
controlElement.closest('LN0,LN')?.getAttribute('lnClass') ?? '';
const srcLNInst = controlElement.closest('LN0,LN')?.getAttribute('inst');
const srcCBName = controlElement.getAttribute('name') ?? '';

return createElement(fCDA.ownerDocument, 'ExtRef', {
return cloneElement(extRefElement, {
iedName,
serviceType: serviceTypes[controlBlock.tagName]!,
serviceType: serviceTypes[controlElement.tagName]!,
ldInst,
lnClass,
lnInst,
Expand All @@ -159,7 +259,7 @@ export function createExtRefElement(
srcLDInst,
srcPrefix,
srcLNClass,
srcLNInst,
srcLNInst: srcLNInst ? srcLNInst : null,
srcCBName,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import { nothing } from 'lit-html';
import { translate } from 'lit-translate';

import {
cloneElement,
compareNames,
getDescriptionAttribute,
getNameAttribute,
identity,
newActionEvent,
Replace,
} from '../../../foundation.js';

import { styles } from '../foundation.js';
import { styles, updateExtRefElement } from '../foundation.js';
import { FcdaSelectEvent, getFcdaTitleValue } from './foundation.js';

/**
Expand Down Expand Up @@ -147,6 +150,60 @@ export class ExtRefLaterBindingList extends LitElement {
);
}

/**
* Unsubscribing means removing a list of attributes from the ExtRef Element.
*
* @param extRefElement - The Ext Ref Element to clean from attributes.
*/
private unsubscribe(extRefElement: Element): Replace {
const clonedExtRefElement = cloneElement(extRefElement, {
iedName: null,
ldInst: null,
prefix: null,
lnClass: null,
lnInst: null,
doName: null,
daName: null,
serviceType: null,
srcLDInst: null,
srcPrefix: null,
srcLNClass: null,
srcLNInst: null,
srcCBName: null,
});

return {
old: { element: extRefElement },
new: { element: clonedExtRefElement },
};
}

/**
* Subscribing means copying a list of attributes from the FCDA Element (and others) to the ExtRef Element.
*
* @param extRefElement - The Ext Ref Element to add the attributes to.
*/
private subscribe(extRefElement: Element): Replace | null {
if (
!this.currentIedElement ||
!this.currentSelectedFcdaElement ||
!this.currentSelectedSvcElement!
) {
return null;
}

return {
old: { element: extRefElement },
new: {
element: updateExtRefElement(
extRefElement,
this.currentSelectedSvcElement,
this.currentSelectedFcdaElement
),
},
};
}

private renderTitle(): TemplateResult {
const svcName = this.currentSelectedSvcElement
? getNameAttribute(this.currentSelectedSvcElement)
Expand Down Expand Up @@ -185,6 +242,11 @@ export class ExtRefLaterBindingList extends LitElement {
extRefElement => html` <mwc-list-item
graphic="large"
twoline
@click=${() => {
this.dispatchEvent(
newActionEvent(this.unsubscribe(extRefElement))
);
}}
value="${identity(extRefElement)}"
>
<span>
Expand Down Expand Up @@ -230,6 +292,12 @@ export class ExtRefLaterBindingList extends LitElement {
graphic="large"
?disabled=${this.unsupportedExtRefElement(extRefElement)}
twoline
@click=${() => {
const replaceAction = this.subscribe(extRefElement);
if (replaceAction) {
this.dispatchEvent(newActionEvent(replaceAction));
}
}}
value="${identity(extRefElement)}"
>
<span>
Expand Down
10 changes: 10 additions & 0 deletions src/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,16 @@ export function referencePath(element: Element): string {
return path;
}

export type SclEdition = '2003' | '2007B' | '2007B4';
export function getSclSchemaVersion(doc: Document): SclEdition {
const scl: Element = doc.documentElement;
const edition =
(scl.getAttribute('version') ?? '2003') +
(scl.getAttribute('revision') ?? '') +
(scl.getAttribute('release') ?? '');
return <SclEdition>edition;
}

/**
* Extract the 'name' attribute from the given XML element.
* @param element - The element to extract name from.
Expand Down
5 changes: 2 additions & 3 deletions test/integration/__snapshots__/open-scd.test.snap.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* @web/test-runner snapshot v1 */
export const snapshots = {};

snapshots["open-scd looks like its snapshot"] =
`<mwc-drawer
snapshots['open-scd looks like its snapshot'] = `<mwc-drawer
class="mdc-theme--surface"
hasheader=""
id="menu"
Expand Down Expand Up @@ -659,6 +658,7 @@ snapshots["open-scd looks like its snapshot"] =
hasmeta=""
left=""
mwc-list-item=""
selected=""
tabindex="-1"
value="/src/editors/SMVSubscriberLaterBinding.js"
>
Expand Down Expand Up @@ -1364,4 +1364,3 @@ snapshots["open-scd looks like its snapshot"] =
</mwc-dialog>
`;
/* end snapshot open-scd looks like its snapshot */

Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { expect, fixture, html } from '@open-wc/testing';

import '../../../../mock-wizard.js';
import '../../mock-wizard.js';

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

import { Editing } from '../../../../../src/Editing.js';
import { Wizarding } from '../../../../../src/Wizarding.js';
import GooseControlSubscriptionPlugin from '../../../../../src/editors/GooseControlSubscription.js';
import { Editing } from '../../../src/Editing.js';
import { Wizarding } from '../../../src/Wizarding.js';
import GooseControlSubscriptionPlugin from '../../../src/editors/GooseControlSubscription.js';

describe('GOOSE subscriber plugin', () => {
customElements.define(
Expand Down Expand Up @@ -139,11 +139,9 @@ describe('GOOSE subscriber plugin', () => {
?.getAttribute('inst')}"][srcPrefix="${
fcda.closest('LN0')?.getAttribute('prefix') ?? '' //prefix is mendatory in ExtRef!!
}"][srcLNClass="${fcda
.closest('LN0')
?.getAttribute('lnClass')}"][srcLNInst="${fcda
.closest('LN0')
?.getAttribute(
'inst'
'lnClass'
)}"][srcCBName="${gseControlBlock.getAttribute(
'name'
)}"][serviceType="GOOSE"]`
Expand Down Expand Up @@ -334,11 +332,9 @@ describe('GOOSE subscriber plugin', () => {
?.getAttribute('inst')}"][srcPrefix="${
fcda.closest('LN0')?.getAttribute('prefix') ?? '' //prefix is mendatory in ExtRef!!
}"][srcLNClass="${fcda
.closest('LN0')
?.getAttribute('lnClass')}"][srcLNInst="${fcda
.closest('LN0')
?.getAttribute(
'inst'
'lnClass'
)}"][srcCBName="${gseControlBlock.getAttribute(
'name'
)}"][serviceType="GOOSE"]`
Expand Down
Loading

0 comments on commit b25f9a6

Please sign in to comment.