diff --git a/packages/plugins/src/editors/subscription/foundation.ts b/packages/plugins/src/editors/subscription/foundation.ts index bac28d2b2a..739678d5df 100644 --- a/packages/plugins/src/editors/subscription/foundation.ts +++ b/packages/plugins/src/editors/subscription/foundation.ts @@ -132,6 +132,13 @@ export function getExtRef( control: Element | undefined ): Element | undefined { function createCriteria(attributeName: string, value: string | null): string { + // For ExtRef the attribute 'srcLNClass' is optional and defaults to 'LLN0', here we ignore 'srcLNClass' completely for 'LLN0' + // because otherwise we would have to extend the querySelector to multiple selector groups checking for 'LLN0' or missing 'srcLNClass' + const shouldIgnoreCriteria = attributeName === 'srcLNClass' && value === 'LLN0'; + if (shouldIgnoreCriteria) { + return ''; + } + if (value) { return `[${attributeName}="${value}"]`; } diff --git a/packages/plugins/src/editors/subscription/later-binding/foundation.ts b/packages/plugins/src/editors/subscription/later-binding/foundation.ts index 4d935b212a..d9fef817fd 100644 --- a/packages/plugins/src/editors/subscription/later-binding/foundation.ts +++ b/packages/plugins/src/editors/subscription/later-binding/foundation.ts @@ -163,6 +163,11 @@ function checkEditionSpecificRequirements( const lDeviceElement = controlElement?.closest('LDevice') ?? undefined; const lnElement = controlElement?.closest('LN0') ?? undefined; + // If ExtRef is missing 'srcLNClass', it defaults to 'LLN0' as specified in the standard + const extRefIsMissingSrcLNClass = !extRefElement.hasAttribute('srcLNClass'); + const isLnClassLLN0 = lnElement?.getAttribute('lnClass') === 'LLN0'; + const canIgnoreSrcLNClass = isLnClassLLN0 && extRefIsMissingSrcLNClass; + // For the 2007B and 2007B4 Edition we need to check some extra attributes. return ( (extRefElement.getAttribute('serviceType') ?? '') === @@ -179,12 +184,12 @@ function checkEditionSpecificRequirements( lnElement, 'prefix' ) && - sameAttributeValueDiffName( + (canIgnoreSrcLNClass || sameAttributeValueDiffName( extRefElement, 'srcLNClass', lnElement, 'lnClass' - ) && + )) && sameAttributeValueDiffName(extRefElement, 'srcLNInst', lnElement, 'inst') && sameAttributeValueDiffName( extRefElement, diff --git a/packages/plugins/test/testfiles/editors/ExtRefWithoutSrcLNClass.scd b/packages/plugins/test/testfiles/editors/ExtRefWithoutSrcLNClass.scd new file mode 100644 index 0000000000..9ecc2e7fc2 --- /dev/null +++ b/packages/plugins/test/testfiles/editors/ExtRefWithoutSrcLNClass.scd @@ -0,0 +1,356 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GOOSE_Subscriber + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + + status-only + + + + + + + + + + + + + + + sbo-with-enhanced-security + + + 30000 + + + 600 + + + + + + + + + + + + + + + + + + + + + + + + + IEC 61850-7-4:2007B4 + + + + + + + + + + + + + + + + + + + sbo-with-enhanced-security + + + 30000 + + + 600 + + + + + + + + + + + + + + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + Load Break + Disconnector + Earthing Switch + High Speed Earthing Switch + + + status-only + + + pulse + persistent + persistent-feedback + + + Ok + Warning + Alarm + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + on + blocked + test + test/blocked + off + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + + diff --git a/packages/plugins/test/unit/editors/subscription/foundation.test.ts b/packages/plugins/test/unit/editors/subscription/foundation.test.ts index e4a66692f4..8d74983f6e 100644 --- a/packages/plugins/test/unit/editors/subscription/foundation.test.ts +++ b/packages/plugins/test/unit/editors/subscription/foundation.test.ts @@ -3,6 +3,7 @@ import { expect } from '@open-wc/testing'; import { createExtRefElement, getExistingSupervision, + getExtRef, getFirstSubscribedExtRef, instantiatedSupervisionsCount, updateExtRefElement, @@ -285,4 +286,52 @@ describe('foundation', () => { expect(count).to.equal(0); }); }); + + describe('use default value for ExtRef without srcLNClass attribute', () => { + beforeEach(async () => { + doc = await fetch('/test/testfiles/editors/ExtRefWithoutSrcLNClass.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + }); + + it('should correctly fetch ExtRef with srcLNClass attribute', () => { + const inputs = doc.querySelector( + 'LDevice[inst="Earth_Switch"] LN0 Inputs' + )!; + const fcda = doc.querySelector( + 'FCDA[ldInst="QB2_Disconnector"][doName="Pos"][daName="stVal"]' + )!; + const controlBlock = doc.querySelector( + 'IED[name="Publisher"] GSEControl[name="GOOSE2"]' + )!; + + const expectedExtRef = doc.querySelector( + 'ExtRef[iedName="Publisher"][ldInst="QB2_Disconnector"][doName="Pos"][daName="stVal"][srcLNClass="LLN0"]' + )!; + + const extRef = getExtRef(inputs, fcda, controlBlock); + + expect(extRef).to.equal(expectedExtRef) + }); + + it('should correctly fetch ExtRef without srcLNClass attribute', () => { + const inputs = doc.querySelector( + 'LDevice[inst="Earth_Switch"] LN0 Inputs' + )!; + const fcda = doc.querySelector( + 'FCDA[ldInst="QB2_Disconnector"][doName="Pos"][daName="q"]' + )!; + const controlBlock = doc.querySelector( + 'IED[name="Publisher"] GSEControl[name="GOOSE2"]' + )!; + + const expectedExtRef = doc.querySelector( + 'ExtRef[iedName="Publisher"][ldInst="QB2_Disconnector"][doName="Pos"][daName="q"]' + )!; + + const extRef = getExtRef(inputs, fcda, controlBlock); + + expect(extRef).to.equal(expectedExtRef) + }); + }); }); diff --git a/packages/plugins/test/unit/editors/subscription/later-binding/foundation.test.ts b/packages/plugins/test/unit/editors/subscription/later-binding/foundation.test.ts index 415df13f60..f42879c11d 100644 --- a/packages/plugins/test/unit/editors/subscription/later-binding/foundation.test.ts +++ b/packages/plugins/test/unit/editors/subscription/later-binding/foundation.test.ts @@ -322,3 +322,44 @@ describe('FCDA specification', () => { bType: 'INT32', })); }); + +describe('use default value for ExtRef without srcLNClass attribute', () => { + let doc: XMLDocument; + beforeEach(async () => { + doc = await fetch('/test/testfiles/editors/ExtRefWithoutSrcLNClass.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + }); + + it('should correctly consider ExtRef with srcLNClass attribute as subscribed', () => { + const fcda = doc.querySelector( + 'FCDA[ldInst="QB2_Disconnector"][doName="Pos"][daName="stVal"]' + )!; + const controlBlock = doc.querySelector( + 'IED[name="Publisher"] GSEControl[name="GOOSE2"]' + )!; + + const extRef = doc.querySelector( + 'ExtRef[iedName="Publisher"][ldInst="QB2_Disconnector"][doName="Pos"][daName="stVal"][srcLNClass="LLN0"]' + )!; + + const isSubscribed = isSubscribedTo('GSEControl', controlBlock, fcda, extRef); + expect(isSubscribed).to.be.true; + }); + + it('should correctly consider ExtRef without srcLNClass attribute as subscribed', () => { + const fcda = doc.querySelector( + 'FCDA[ldInst="QB2_Disconnector"][doName="Pos"][daName="q"]' + )!; + const controlBlock = doc.querySelector( + 'IED[name="Publisher"] GSEControl[name="GOOSE2"]' + )!; + + const extRef = doc.querySelector( + 'ExtRef[iedName="Publisher"][ldInst="QB2_Disconnector"][doName="Pos"][daName="q"]' + )!; + + const isSubscribed = isSubscribedTo('GSEControl', controlBlock, fcda, extRef); + expect(isSubscribed).to.be.true; + }); +});