Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editors/subscriber/later-binding): add input requirement check #1049

Merged
merged 2 commits into from
Oct 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ import {
newSubscriptionChangedEvent,
styles,
updateExtRefElement,
serviceTypes,
} from '../foundation.js';

import {
getExtRefElements,
getSubscribedExtRefElements,
fcdaSpecification,
inputRestriction,
isSubscribed,
} from './foundation.js';

Expand Down Expand Up @@ -70,19 +74,40 @@ export class ExtRefLaterBindingList extends LitElement {
}

/**
* The data attribute check using attributes pLN, pDO, pDA and pServT is not supported yet by this plugin.
* To make sure the user does not do anything prohibited, this type of ExtRef cannot be manipulated for the time being.
* (Will be updated in the future).
* Check data consistency of source `FCDA` and sink `ExtRef` based on
* `ExtRef`'s `pLN`, `pDO`, `pDA` and `pServT` attributes.
* Consistent means `CDC` and `bType` of both ExtRef and FCDA is equal.
* In case
* - `pLN`, `pDO`, `pDA` or `pServT` attributes are not present, allow subscribing
* - no CDC or bType can be extracted, do not allow subscribing
*
* @param extRefElement - The Ext Ref Element to check.
* @param extRef - The `ExtRef` Element to check against
*/
private unsupportedExtRefElement(extRefElement: Element): boolean {
return (
extRefElement.hasAttribute('pLN') ||
extRefElement.hasAttribute('pDO') ||
extRefElement.hasAttribute('pDA') ||
extRefElement.hasAttribute('pServT')
);
private unsupportedExtRefElement(extRef: Element): boolean {
// Vendor does not provide data for the check
if (
!extRef.hasAttribute('pLN') ||
!extRef.hasAttribute('pDO') ||
!extRef.hasAttribute('pDA') ||
!extRef.hasAttribute('pServT')
)
return false;

// Not ready for any kind of subscription
if (!this.currentSelectedFcdaElement) return true;

const fcda = fcdaSpecification(this.currentSelectedFcdaElement);
const input = inputRestriction(extRef);

if (fcda.cdc === null && input.cdc === null) return true;
if (fcda.bType === null && input.bType === null) return true;
if (
serviceTypes[this.currentSelectedControlElement?.tagName ?? ''] !==
extRef.getAttribute('pServT')
)
return true;

return fcda.cdc !== input.cdc || fcda.bType !== input.bType;
}

/**
Expand Down
105 changes: 104 additions & 1 deletion src/editors/subscription/later-binding/foundation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,110 @@
import { getSclSchemaVersion } from '../../../foundation.js';

import { serviceTypes } from '../foundation.js';

function dataAttributeSpecification(
anyLn: Element,
doName: string,
daName: string
): { cdc: string | null; bType: string | null } {
const doc = anyLn.ownerDocument;
const lNodeType = doc.querySelector(
`LNodeType[id="${anyLn.getAttribute('lnType')}"]`
);

const doNames = doName.split('.');
let leaf: Element | null | undefined = lNodeType;
for (const doName of doNames) {
const dO: Element | null | undefined = leaf?.querySelector(
`DO[name="${doName}"], SDO[name="${doName}"]`
);
leaf = doc.querySelector(`DOType[id="${dO?.getAttribute('type')}"]`);
}
if (!leaf || !leaf.getAttribute('cdc')) return { cdc: null, bType: null };
JakobVogelsang marked this conversation as resolved.
Show resolved Hide resolved

const cdc = leaf.getAttribute('cdc')!;

const daNames = daName.split('.');
for (const daName of daNames) {
const dA: Element | null | undefined = leaf?.querySelector(
`DA[name="${daName}"], BDA[name="${daName}"]`
);
leaf =
daNames.indexOf(daName) < daNames.length - 1
? doc.querySelector(`DAType[id="${dA?.getAttribute('type')}"]`)
: dA;
}
if (!leaf || !leaf.getAttribute('bType')) return { cdc, bType: null };

const bType = leaf.getAttribute('bType')!;

return { bType, cdc };
}

/**
* @param fcda - Data attribute reference in a data set
* @returns Data objects `CDC` and data attributes `bType`
*/
export function fcdaSpecification(fcda: Element): {
cdc: string | null;
bType: string | null;
} {
const [doName, daName] = ['doName', 'daName'].map(attr =>
fcda.getAttribute(attr)
);
if (!doName || !daName) return { cdc: null, bType: null };

const ied = fcda.closest('IED');

const anyLn = Array.from(
ied?.querySelectorAll(
`LDevice[inst="${fcda.getAttribute(
'ldInst'
)}"] > LN, LDevice[inst="${fcda.getAttribute('inst')}"] LN0`
) ?? []
).find(anyLn => {
return (
(anyLn.getAttribute('prefix') ?? '') ===
(fcda.getAttribute('prefix') ?? '') &&
(anyLn.getAttribute('lnClass') ?? '') ===
(fcda.getAttribute('lnClass') ?? '') &&
(anyLn.getAttribute('inst') ?? '') === (fcda.getAttribute('lnInst') ?? '')
);
});
if (!anyLn) return { cdc: null, bType: null };

return dataAttributeSpecification(anyLn, doName, daName);
}

/**
* Edition 2 and later SCL files allow to restrict subscription on
* later binding type inputs (`ExtRef` elements) based on a `CDC` and
* basic type `bType`.
* @param extRef - A later binding type input in the sink IED
* @returns data objects `CDC` and data attribute basic type `bType` or `null`
*/
export function inputRestriction(extRef: Element): {
cdc: string | null;
bType: string | null;
} {
const [pLN, pDO, pDA] = ['pLN', 'pDO', 'pDA'].map(attr =>
extRef.getAttribute(attr)
);
if (!pLN || !pDO || !pDA) return { cdc: null, bType: null };

const anyLns = Array.from(
extRef
.closest('IED')
?.querySelectorAll(`LN[lnClass="${pLN}"],LN0[lnClass="${pLN}"]`) ?? []
);

for (const anyLn of anyLns) {
const dataSpec = dataAttributeSpecification(anyLn, pDO, pDA);
if (dataSpec.cdc !== null && dataSpec.bType !== null) return dataSpec;
}

return { cdc: null, bType: null };
}

/**
* Simple function to check if the attribute of the Left Side has the same value as the attribute of the Right Element.
*
Expand Down
8 changes: 4 additions & 4 deletions test/integration/editors/GooseSubscriberLaterBinding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('GOOSE Subscribe Later Binding Plugin', () => {
expect(getSelectedSubItemValue(fcdaListElement)).to.be.null;
expect(
extRefListElement['getAvailableExtRefElements']().length
).to.be.equal(4);
).to.be.equal(5);

(<HTMLElement>(
extRefListElement.shadowRoot!.querySelector(
Expand All @@ -64,7 +64,7 @@ describe('GOOSE Subscribe Later Binding Plugin', () => {
expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('1');
expect(
extRefListElement['getAvailableExtRefElements']().length
).to.be.equal(3);
).to.be.equal(4);
});

it('when unsubscribing a subscribed ExtRef then the lists are changed', async () => {
Expand All @@ -85,7 +85,7 @@ describe('GOOSE Subscribe Later Binding Plugin', () => {
expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('2');
expect(
extRefListElement['getAvailableExtRefElements']().length
).to.be.equal(4);
).to.be.equal(5);

(<HTMLElement>(
extRefListElement.shadowRoot!.querySelector(
Expand All @@ -100,6 +100,6 @@ describe('GOOSE Subscribe Later Binding Plugin', () => {
expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('1');
expect(
extRefListElement['getAvailableExtRefElements']().length
).to.be.equal(5);
).to.be.equal(6);
});
});
8 changes: 4 additions & 4 deletions test/integration/editors/SMVSubscriberLaterBinding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('SMV Subscribe Later Binding plugin', () => {
expect(getSelectedSubItemValue(fcdaListElement)).to.be.null;
expect(
extRefListElement['getAvailableExtRefElements']().length
).to.be.equal(8);
).to.be.equal(9);

(<HTMLElement>(
extRefListElement.shadowRoot!.querySelector(
Expand All @@ -65,7 +65,7 @@ describe('SMV Subscribe Later Binding plugin', () => {
expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('1');
expect(
extRefListElement['getAvailableExtRefElements']().length
).to.be.equal(7);
).to.be.equal(8);
});

it('when unsubscribing a subscribed ExtRef then the lists are changed', async () => {
Expand All @@ -86,7 +86,7 @@ describe('SMV Subscribe Later Binding plugin', () => {
expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('3');
expect(
extRefListElement['getAvailableExtRefElements']().length
).to.be.equal(8);
).to.be.equal(9);

(<HTMLElement>(
extRefListElement.shadowRoot!.querySelector(
Expand All @@ -101,6 +101,6 @@ describe('SMV Subscribe Later Binding plugin', () => {
expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('2');
expect(
extRefListElement['getAvailableExtRefElements']().length
).to.be.equal(9);
).to.be.equal(10);
});
});
5 changes: 3 additions & 2 deletions test/testfiles/editors/LaterBindingGOOSE2007B4.scd
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
<Inputs>
<ExtRef iedName="GOOSE_Publisher" serviceType="GOOSE" ldInst="QB2_Disconnector" lnClass="CSWI" lnInst="1" prefix="" doName="Pos" daName="stVal" srcLDInst="QB2_Disconnector" srcPrefix="" srcLNClass="LLN0" srcCBName="GOOSE2" intAddr="Pos;CSWI1/Pos/stVal" desc="Interlocking.Input2"/>
<ExtRef iedName="GOOSE_Publisher" serviceType="GOOSE" ldInst="QB2_Disconnector" lnClass="CSWI" lnInst="1" prefix="" doName="Pos" daName="q" srcLDInst="QB2_Disconnector" srcPrefix="" srcLNClass="LLN0" srcCBName="GOOSE2" intAddr="Pos;CSWI1/Pos/q" desc="Interlocking.Input2"/>
<ExtRef intAddr="someRestrictedExtRef" desc="Restricted To Pos" pLN="CSWI" pDO="Pos" pDA="stVal"/>
<ExtRef intAddr="someRestrictedExtRef" desc="Restricted To Pos" pLN="CSWI" pDO="Pos" pDA="q" pServT="GOOSE"/>
<ExtRef intAddr="someRestrictedExtRef" desc="Restricted To Pos" pLN="CSWI" pDO="Pos" pDA="stVal" pServT="GOOSE"/>
</Inputs>
</LN>
<LN prefix="" lnClass="XSWI" inst="1" lnType="Dummy.XSWI"/>
Expand Down Expand Up @@ -344,4 +345,4 @@
<EnumVal ord="8">process</EnumVal>
</EnumType>
</DataTypeTemplates>
</SCL>
</SCL>
9 changes: 8 additions & 1 deletion test/testfiles/editors/LaterBindingSMV2007B4.scd
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,17 @@
<ExtRef intAddr="VolSv;TVTR2/VolSv/q" desc="MeasPoint.VT1"/>
<ExtRef intAddr="VolSv;TVTR3/VolSv/instMag.i" desc="MeasPoint.VT3"/>
<ExtRef intAddr="VolSv;TVTR3/VolSv/q" desc="MeasPoint.VT1"/>
<ExtRef intAddr="someRestrictedExtRef" desc="Restricted To AmpSV" pLN="TCTR" pDO="AmpSV" pDA="instMag.i"/>
<ExtRef intAddr="someRestrictedExtRef" desc="Restricted To AmpSV" pLN="TCTR" pDO="AmpSv" pDA="q" pServT="SMV"/>
<ExtRef intAddr="someRestrictedExtRef" desc="Restricted To AmpSV" pLN="TCTR" pDO="AmpSv" pDA="instMag.i" pServT="SMV"/>
</Inputs>
</LN>
</LDevice>
<LDevice inst="CurrentTrans">
<LN prefix="L1" lnClass="TCTR" inst="1" lnType="Dummy.TCTR"/>
<LN prefix="L2" lnClass="TCTR" inst="1" lnType="Dummy.TCTR"/>
<LN prefix="L3" lnClass="TCTR" inst="1" lnType="Dummy.TCTR"/>
<LN prefix="RES" lnClass="TCTR" inst="1" lnType="Dummy.TCTR"/>
</LDevice>
</Server>
</AccessPoint>
</IED>
Expand Down
Loading