diff --git a/src/editors/subscription/foundation.ts b/src/editors/subscription/foundation.ts
index c32acff61..5867acc3c 100644
--- a/src/editors/subscription/foundation.ts
+++ b/src/editors/subscription/foundation.ts
@@ -197,6 +197,37 @@ export function canRemoveSubscriptionSupervision(
);
}
+/**
+ * Searches DataTypeTemplates for DOType>DA[valKind=Conf/RO][valImport=true] from an LN reference.
+ * @param lnElement - The LN Element to use for searching the starting DO Element.
+ * @returns - true if both conditions are found in the DA child element.
+ */
+function checksDataTypeTemplateConditions(lnElement: Element): boolean {
+ const rootNode = lnElement?.ownerDocument;
+ const lNodeType = lnElement.getAttribute('lnType');
+ const lnClass = lnElement.getAttribute('lnClass');
+ const dObj = rootNode.querySelector(
+ `DataTypeTemplates > LNodeType[id="${lNodeType}"][lnClass="${lnClass}"] > DO[name="${
+ lnClass === 'LGOS' ? 'GoCBRef' : 'SvCBRef'
+ }"]`
+ );
+ if (dObj) {
+ const dORef = dObj.getAttribute('type');
+ const daObj = rootNode.querySelector(
+ `DataTypeTemplates > DOType[id="${dORef}"] > DA[name="setSrcRef"]`
+ );
+ if (daObj) {
+ return (
+ (daObj.getAttribute('valKind') === 'Conf' ||
+ daObj.getAttribute('valKind') === 'RO') &&
+ daObj.getAttribute('valImport') === 'true'
+ );
+ }
+ }
+ // definition missing
+ return false;
+}
+
/**
* Returns an array with a single Create action to create a new
* supervision element for the given GOOSE/SMV message and subscriber IED.
@@ -222,7 +253,8 @@ export function instantiateSubscriptionSupervision(
subscriberIED,
supervisionType
);
- if (!availableLN) return [];
+ if (!availableLN || !checksDataTypeTemplateConditions(availableLN)) return [];
+
// Then, create the templateStructure array
const templateStructure = createTemplateStructure(availableLN, [
controlBlock?.tagName === 'GSEControl' ? 'GoCBRef' : 'SvCBRef',
diff --git a/test/testfiles/editors/VS893-LaterBindingSMV-LSVS.scd b/test/testfiles/editors/VS893-LaterBindingSMV-LSVS.scd
index b70a82960..98ed7bb1d 100644
--- a/test/testfiles/editors/VS893-LaterBindingSMV-LSVS.scd
+++ b/test/testfiles/editors/VS893-LaterBindingSMV-LSVS.scd
@@ -73,7 +73,7 @@
-
+
@@ -128,7 +128,7 @@
-
+
@@ -162,16 +162,16 @@
-
+
SMV_PublisherCurrentTransformer/LLN0.currrentOnly
-
-
-
+
+
+
@@ -210,6 +210,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SMV_PublisherCurrentTransformer/LLN0.currrentOnly
+
+
+
+
+
+
+
+
+
+
@@ -524,6 +574,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -565,6 +631,12 @@
+
+
+
+
+
+
diff --git a/test/testfiles/editors/VS893_LaterBindingGOOSE-LGOS.scd b/test/testfiles/editors/VS893_LaterBindingGOOSE-LGOS.scd
index 93882c69c..a339c36e1 100644
--- a/test/testfiles/editors/VS893_LaterBindingGOOSE-LGOS.scd
+++ b/test/testfiles/editors/VS893_LaterBindingGOOSE-LGOS.scd
@@ -77,6 +77,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -125,6 +154,14 @@
+
+
+
+
+ GOOSE_PublisherQB2_Disconnector/LLN0.GOOSE1
+
+
+
@@ -167,37 +204,31 @@
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -270,6 +301,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -309,6 +356,12 @@
+
+
+
+
+
+
diff --git a/test/unit/editors/subscription/supervision.test.ts b/test/unit/editors/subscription/supervision.test.ts
index dcfc7ce54..fe2c7be11 100644
--- a/test/unit/editors/subscription/supervision.test.ts
+++ b/test/unit/editors/subscription/supervision.test.ts
@@ -21,87 +21,108 @@ describe('supervision', () => {
.then(str => new DOMParser().parseFromString(str, 'application/xml'));
});
- it('produces the correct structure where supervision is allowed', () => {
- controlElement = doc.querySelector(
- 'IED[name="SMV_Publisher4"] SampledValueControl[name="voltageOnly"]'
- )!;
- subscriberIED = doc.querySelector('IED[name="SMV_Subscriber3"]')!;
- const noSupervisionAllowedActions: Create[] =
- instantiateSubscriptionSupervision(controlElement, subscriberIED);
- expect(noSupervisionAllowedActions).to.have.length(0);
-
- subscriberIED = doc.querySelector('IED[name="SMV_Subscriber2"]')!;
- const createActions: Create[] = instantiateSubscriptionSupervision(
- controlElement,
- subscriberIED
- );
- expect(createActions).to.have.length(1);
- const createAction: Create = createActions[0];
- const parentElement: Node = createAction.new.parent;
- const newElement: Node = createAction.new.element;
- expect(parentElement).to.exist;
- expect(newElement).to.exist;
- expect(parentElement.nodeName).to.be.equal('LN');
- expect(newElement.nodeName).to.be.equal('DOI');
-
- controlElement = doc.querySelector(
- 'IED[name="SMV_Publisher3"] SampledValueControl[name="fullSmv"]'
- )!;
- subscriberIED = doc.querySelector('IED[name="SMV_Subscriber"]')!;
-
- const createActionsWithNewLn: Create[] =
- instantiateSubscriptionSupervision(controlElement, subscriberIED);
- expect(createActionsWithNewLn).to.have.length(2);
- const newLnAction: Create = createActionsWithNewLn[0];
- const lDeviceParent: Node = newLnAction.new.parent;
- const newLnElement: Node = newLnAction.new.element;
- expect(lDeviceParent).to.exist;
- expect(newLnElement).to.exist;
- expect(lDeviceParent.nodeName).to.be.equal('LDevice');
- expect(newLnElement.nodeName).to.be.equal('LN');
-
- const newDoiAction: Create = createActionsWithNewLn[1];
- const lnParent: Node = newDoiAction.new.parent;
- const newDoiElement: Node = newDoiAction.new.element;
- expect(lnParent).to.exist;
- expect(newDoiElement).to.exist;
- expect(lnParent.nodeName).to.be.equal('LN');
- expect(newDoiElement.nodeName).to.be.equal('DOI');
+ describe('produces the correct structure where supervision is allowed', () => {
+ beforeEach(() => {
+ controlElement = doc.querySelector(
+ 'IED[name="SMV_Publisher4"] SampledValueControl[name="voltageOnly"]'
+ )!;
+ });
+
+ it('does not allow supervision because there is no services defined in the IED', () => {
+ subscriberIED = doc.querySelector('IED[name="SMV_Subscriber3"]')!;
+ const noSupervisionAllowedActions: Create[] =
+ instantiateSubscriptionSupervision(controlElement!, subscriberIED);
+ expect(noSupervisionAllowedActions).to.have.length(0);
+ });
+
+ it('does not allow supervision because the DataTypeTemplate>DOType>DA is missing necessary attributes ', () => {
+ subscriberIED = doc.querySelector('IED[name="SMV_Subscriber4"]')!;
+ const noSupervisionAllowedActions: Create[] =
+ instantiateSubscriptionSupervision(controlElement!, subscriberIED);
+ expect(noSupervisionAllowedActions).to.have.length(0);
+ });
+
+ it('allows supervision and an existing LN[class=LSVS] should be used', () => {
+ subscriberIED = doc.querySelector('IED[name="SMV_Subscriber2"]')!;
+ const createActions: Create[] = instantiateSubscriptionSupervision(
+ controlElement!,
+ subscriberIED
+ );
+ expect(createActions).to.have.length(1);
+ const createAction: Create = createActions[0];
+ const parentElement: Node = createAction.new.parent;
+ const newElement: Node = createAction.new.element;
+ expect(parentElement).to.exist;
+ expect(newElement).to.exist;
+ expect(parentElement.nodeName).to.be.equal('LN');
+ expect(newElement.nodeName).to.be.equal('DOI');
+ });
+
+ it('allows supervision and an a new LN[class=LSVS] should be created as well', () => {
+ controlElement = doc.querySelector(
+ 'IED[name="SMV_Publisher3"] SampledValueControl[name="fullSmv"]'
+ )!;
+ subscriberIED = doc.querySelector('IED[name="SMV_Subscriber"]')!;
+ const createActionsWithNewLn: Create[] =
+ instantiateSubscriptionSupervision(controlElement, subscriberIED);
+ expect(createActionsWithNewLn).to.have.length(2);
+ const newLnAction: Create = createActionsWithNewLn[0];
+ const lDeviceParent: Node = newLnAction.new.parent;
+ const newLnElement: Node = newLnAction.new.element;
+ expect(lDeviceParent).to.exist;
+ expect(newLnElement).to.exist;
+ expect(lDeviceParent.nodeName).to.be.equal('LDevice');
+ expect(newLnElement.nodeName).to.be.equal('LN');
+
+ const newDoiAction: Create = createActionsWithNewLn[1];
+ const lnParent: Node = newDoiAction.new.parent;
+ const newDoiElement: Node = newDoiAction.new.element;
+ expect(lnParent).to.exist;
+ expect(newDoiElement).to.exist;
+ expect(lnParent.nodeName).to.be.equal('LN');
+ expect(newDoiElement.nodeName).to.be.equal('DOI');
+ });
});
it('removes the correct elements when supervision is removed', () => {
- controlElement = doc.querySelector(
- 'IED[name="SMV_Publisher2"] SampledValueControl[name="fullSmv"]'
- )!;
- subscriberIED = doc.querySelector('IED[name="SMV_Subscriber"]')!;
- const removeActions: Delete[] = removeSubscriptionSupervision(
- controlElement,
- subscriberIED
- );
- expect(removeActions).to.have.length(1);
- const removeAction: Delete = removeActions[0];
- const parentElement: Node = removeAction.old.parent;
- const element: Node = removeAction.old.element;
- expect(parentElement).to.exist;
- expect(element).to.exist;
- expect(parentElement.nodeName).to.be.equal('LN');
- expect(element.nodeName).to.be.equal('DOI');
-
- controlElement = doc.querySelector(
- 'IED[name="SMV_Publisher2"] SampledValueControl[name="voltageOnly"]'
- )!;
- const removeActionsWithRemovedLn: Delete[] =
- removeSubscriptionSupervision(controlElement, subscriberIED);
- expect(removeActionsWithRemovedLn).to.have.length(1);
- const removeLnAction: Delete = removeActionsWithRemovedLn[0];
- const lnParent: Node = removeLnAction.old.parent;
- const lnElement: Node = removeLnAction.old.element;
- expect(lnParent).to.exist;
- expect(lnElement).to.exist;
- expect(lnParent.nodeName).to.be.equal('LDevice');
- expect(lnElement.nodeName).to.be.equal('LN');
+ beforeEach(() => {
+ subscriberIED = doc.querySelector('IED[name="SMV_Subscriber"]')!;
+ });
+ it('allows removing supervision in a vendor-created LN', () => {
+ controlElement = doc.querySelector(
+ 'IED[name="SMV_Publisher2"] SampledValueControl[name="fullSmv"]'
+ )!;
+ const removeActions: Delete[] = removeSubscriptionSupervision(
+ controlElement,
+ subscriberIED!
+ );
+ expect(removeActions).to.have.length(1);
+ const removeAction: Delete = removeActions[0];
+ const parentElement: Node = removeAction.old.parent;
+ const element: Node = removeAction.old.element;
+ expect(parentElement).to.exist;
+ expect(element).to.exist;
+ expect(parentElement.nodeName).to.be.equal('LN');
+ expect(element.nodeName).to.be.equal('DOI');
+ });
+ it('allows removing supervision in an OpenSCD-created LN', () => {
+ controlElement = doc.querySelector(
+ 'IED[name="SMV_Publisher2"] SampledValueControl[name="voltageOnly"]'
+ )!;
+ const removeActionsWithRemovedLn: Delete[] =
+ removeSubscriptionSupervision(controlElement, subscriberIED!);
+ expect(removeActionsWithRemovedLn).to.have.length(1);
+ const removeLnAction: Delete = removeActionsWithRemovedLn[0];
+ const lnParent: Node = removeLnAction.old.parent;
+ const lnElement: Node = removeLnAction.old.element;
+ expect(lnParent).to.exist;
+ expect(lnElement).to.exist;
+ expect(lnParent.nodeName).to.be.equal('LDevice');
+ expect(lnElement.nodeName).to.be.equal('LN');
+ });
});
});
+
describe('when subscribing to a GSEControl', () => {
beforeEach(async () => {
doc = await fetch(
@@ -111,85 +132,112 @@ describe('supervision', () => {
.then(str => new DOMParser().parseFromString(str, 'application/xml'));
});
- it('produces the correct structure where supervision is allowed', () => {
- controlElement = doc.querySelector(
- 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE1"]'
- )!;
- subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber4"]')!;
- const noSupervisionAllowedActions: Create[] =
- instantiateSubscriptionSupervision(controlElement, subscriberIED);
- expect(noSupervisionAllowedActions).to.have.length(0);
-
- subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber2"]')!;
- const createActions: Create[] = instantiateSubscriptionSupervision(
- controlElement,
- subscriberIED
- );
- expect(createActions).to.have.length(1);
- const createAction: Create = createActions[0];
- const parentElement: Node = createAction.new.parent;
- const newElement: Node = createAction.new.element;
- expect(parentElement).to.exist;
- expect(newElement).to.exist;
- expect(parentElement.nodeName).to.be.equal('LN');
- expect(newElement.nodeName).to.be.equal('DOI');
-
- controlElement = doc.querySelector(
- 'IED[name="GOOSE_Publisher"] GSEControl[name="GOOSE1"]'
- )!;
- subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber1"]')!;
-
- const createActionsWithNewLn: Create[] =
- instantiateSubscriptionSupervision(controlElement, subscriberIED);
- expect(createActionsWithNewLn).to.have.length(2);
- const newLnAction: Create = createActionsWithNewLn[0];
- const lDeviceParent: Node = newLnAction.new.parent;
- const newLnElement: Node = newLnAction.new.element;
- expect(lDeviceParent).to.exist;
- expect(newLnElement).to.exist;
- expect(lDeviceParent.nodeName).to.be.equal('LDevice');
- expect(newLnElement.nodeName).to.be.equal('LN');
-
- const newDoiAction: Create = createActionsWithNewLn[1];
- const lnParent: Node = newDoiAction.new.parent;
- const newDoiElement: Node = newDoiAction.new.element;
- expect(lnParent).to.exist;
- expect(newDoiElement).to.exist;
- expect(lnParent.nodeName).to.be.equal('LN');
- expect(newDoiElement.nodeName).to.be.equal('DOI');
+ describe('produces the correct structure where supervision is allowed', () => {
+ beforeEach(() => {
+ controlElement = doc.querySelector(
+ 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE1"]'
+ )!;
+ });
+ it('does not allow supervision because there is no services defined in the IED', () => {
+ subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber"]')!;
+ const noSupervisionAllowedActions: Create[] =
+ instantiateSubscriptionSupervision(controlElement!, subscriberIED);
+ expect(noSupervisionAllowedActions).to.have.length(0);
+ });
+ it('does not allow supervision because the DataTypeTemplate>DOType>DA is missing necessary attributes ', () => {
+ subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber4"]')!;
+ const noSupervisionAllowedActions: Create[] =
+ instantiateSubscriptionSupervision(controlElement!, subscriberIED);
+ expect(noSupervisionAllowedActions).to.have.length(0);
+ });
+ it('allows supervision and an existing LN[class=LGOS] should be used', () => {
+ subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber2"]')!;
+ const createActions: Create[] = instantiateSubscriptionSupervision(
+ controlElement!,
+ subscriberIED
+ );
+ expect(createActions).to.have.length(1);
+ const createAction: Create = createActions[0];
+ const parentElement: Node = createAction.new.parent;
+ const newElement: Node = createAction.new.element;
+ expect(parentElement).to.exist;
+ expect(newElement).to.exist;
+ expect(parentElement.nodeName).to.be.equal('LN');
+ expect(newElement.nodeName).to.be.equal('DOI');
+ });
+ it('allows supervision and an a new LN[class=LGOS] should be created as well', () => {
+ controlElement = doc.querySelector(
+ 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE1"]'
+ )!;
+ subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber1"]')!;
+
+ const createActionsWithNewLn: Create[] =
+ instantiateSubscriptionSupervision(controlElement, subscriberIED);
+ expect(createActionsWithNewLn).to.have.length(2);
+ const newLnAction: Create = createActionsWithNewLn[0];
+ const lDeviceParent: Node = newLnAction.new.parent;
+ const newLnElement: Node = newLnAction.new.element;
+ expect(lDeviceParent).to.exist;
+ expect(newLnElement).to.exist;
+ expect(lDeviceParent.nodeName).to.be.equal('LDevice');
+ expect(newLnElement.nodeName).to.be.equal('LN');
+
+ const newDoiAction: Create = createActionsWithNewLn[1];
+ const lnParent: Node = newDoiAction.new.parent;
+ const newDoiElement: Node = newDoiAction.new.element;
+ expect(lnParent).to.exist;
+ expect(newDoiElement).to.exist;
+ expect(lnParent.nodeName).to.be.equal('LN');
+ expect(newDoiElement.nodeName).to.be.equal('DOI');
+ });
});
- it('removes the correct elements when supervision is removed', () => {
- controlElement = doc.querySelector(
- 'IED[name="GOOSE_Publisher"] GSEControl[name="GOOSE2"]'
- )!;
- subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber1"]')!;
- const removeActions: Delete[] = removeSubscriptionSupervision(
- controlElement,
- subscriberIED
- );
- expect(removeActions).to.have.length(1);
- const removeAction: Delete = removeActions[0];
- const parentElement: Node = removeAction.old.parent;
- const element: Node = removeAction.old.element;
- expect(parentElement).to.exist;
- expect(element).to.exist;
- expect(parentElement.nodeName).to.be.equal('LN');
- expect(element.nodeName).to.be.equal('DOI');
-
- controlElement = doc.querySelector(
- 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE2"]'
- )!;
- const removeActionsWithRemovedLn: Delete[] =
- removeSubscriptionSupervision(controlElement, subscriberIED);
- expect(removeActionsWithRemovedLn).to.have.length(1);
- const removeLnAction: Delete = removeActionsWithRemovedLn[0];
- const lnParent: Node = removeLnAction.old.parent;
- const lnElement: Node = removeLnAction.old.element;
- expect(lnParent).to.exist;
- expect(lnElement).to.exist;
- expect(lnParent.nodeName).to.be.equal('LDevice');
- expect(lnElement.nodeName).to.be.equal('LN');
+ describe('removes the correct elements when supervision is removed', () => {
+ beforeEach(() => {
+ subscriberIED = doc.querySelector('IED[name="GOOSE_Subscriber1"]')!;
+ });
+ it('allows removing supervision in a vendor-created LN', () => {
+ controlElement = doc.querySelector(
+ 'IED[name="GOOSE_Publisher"] GSEControl[name="GOOSE2"]'
+ )!;
+ const removeActions: Delete[] = removeSubscriptionSupervision(
+ controlElement,
+ subscriberIED!
+ );
+ expect(removeActions).to.have.length(1);
+ const removeAction: Delete = removeActions[0];
+ const parentElement: Node = removeAction.old.parent;
+ const element: Node = removeAction.old.element;
+ expect(parentElement).to.exist;
+ expect(element).to.exist;
+ expect(parentElement.nodeName).to.be.equal('LN');
+ expect(element.nodeName).to.be.equal('DOI');
+ });
+ it('allows removing supervision in an OpenSCD-created LN', () => {
+ controlElement = doc.querySelector(
+ 'IED[name="GOOSE_Publisher"] GSEControl[name="GOOSE1"]'
+ )!;
+ const removeActionsWithRemovedLn: Delete[] =
+ removeSubscriptionSupervision(controlElement, subscriberIED!);
+ expect(removeActionsWithRemovedLn).to.have.length(1);
+ const removeLnAction: Delete = removeActionsWithRemovedLn[0];
+ const lnParent: Node = removeLnAction.old.parent;
+ const lnElement: Node = removeLnAction.old.element;
+ expect(lnParent).to.exist;
+ expect(lnElement).to.exist;
+ expect(lnParent.nodeName).to.be.equal('LDevice');
+ expect(lnElement.nodeName).to.be.equal('LN');
+ });
+ it('does not allow removing supervision because other data attributes are still using it', () => {
+ controlElement = doc.querySelector(
+ 'IED[name="GOOSE_Publisher2"] GSEControl[name="GOOSE1"]'
+ )!;
+ const removeActions: Delete[] = removeSubscriptionSupervision(
+ controlElement,
+ subscriberIED!
+ );
+ expect(removeActions).to.have.length(0);
+ });
});
});
});