Skip to content

Commit

Permalink
Allow replacement of supervision control block reference, closes #79.
Browse files Browse the repository at this point in the history
  • Loading branch information
danyill committed Jan 28, 2024
1 parent f67d73d commit 8065496
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 30 deletions.
24 changes: 12 additions & 12 deletions tExtRef/doesFcdaMeetExtRefRestrictions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("Function compare FCDA basic types to ExtRef restrictions", () => {
const extRef = findElement(extRefXSWIPosDbPos, "ExtRef") as Element;

expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, { controlBlockType: "SMV" })
doesFcdaMeetExtRefRestrictions(extRef, fcda, { controlBlockType: "SMV" }),
).to.be.false;
});

Expand All @@ -61,7 +61,7 @@ describe("Function compare FCDA basic types to ExtRef restrictions", () => {
expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, {
controlBlockType: "GOOSE",
})
}),
).to.be.false;
});

Expand All @@ -72,21 +72,21 @@ describe("Function compare FCDA basic types to ExtRef restrictions", () => {
expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, {
controlBlockType: "GOOSE",
})
}),
).to.be.false;
});

it("returns false with not matching cdc", () => {
const fcda = findElement(
publisherIED,
`FCDA[desc="USERENSnull"]`
`FCDA[desc="USERENSnull"]`,
) as Element;
const extRef = findElement(extRefPos, "ExtRef") as Element;

expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, {
controlBlockType: "GOOSE",
})
}),
).to.be.false;
});

Expand All @@ -97,35 +97,35 @@ describe("Function compare FCDA basic types to ExtRef restrictions", () => {
expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, {
controlBlockType: "GOOSE",
})
}),
).to.be.false;
});

it("returns false with not matching bType", () => {
const fcda = findElement(
publisherIED,
`FCDA[desc="USERENSstVal"]`
`FCDA[desc="USERENSstVal"]`,
) as Element;
const extRef = findElement(extRefBehstVal, "ExtRef") as Element;

expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, {
controlBlockType: "GOOSE",
})
}),
).to.be.false;
});

it("returns true for matching types CMV/FLOAT32 ", () => {
const fcda = findElement(
publisherIED,
`FCDA[desc="CMVFLOAT32"]`
`FCDA[desc="CMVFLOAT32"]`,
) as Element;
const extRef = findElement(extRefCMVFLOAT32, "ExtRef") as Element;

expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, {
controlBlockType: "Report",
})
}),
).to.be.true;
});

Expand All @@ -136,7 +136,7 @@ describe("Function compare FCDA basic types to ExtRef restrictions", () => {
expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, {
controlBlockType: "GOOSE",
})
}),
).to.be.true;
});

Expand All @@ -155,7 +155,7 @@ describe("Function compare FCDA basic types to ExtRef restrictions", () => {
expect(
doesFcdaMeetExtRefRestrictions(extRef, fcda, {
checkOnlyBType: true,
})
}),
).to.be.true;
});
});
2 changes: 1 addition & 1 deletion tExtRef/doesFcdaMeetExtRefRestrictions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type DoesFcdaMeetExtRefRestrictionsOptions = {
export function doesFcdaMeetExtRefRestrictions(
extRef: Element,
fcda: Element,
options: DoesFcdaMeetExtRefRestrictionsOptions = { checkOnlyBType: false }
options: DoesFcdaMeetExtRefRestrictionsOptions = { checkOnlyBType: false },
): boolean {
// Vendor does not provide data for the check so any FCDA meets restriction
if (!extRef.hasAttribute("pDO")) return true;
Expand Down
15 changes: 15 additions & 0 deletions tLN/supervision/canInstantiateSubscriptionSupervision.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,4 +506,19 @@ describe("Function that checks whether subscription supervision can be instantia
}),
).to.be.true;
});

it("can replace a valid supervision with correct option", () => {
const sourceControlBlock = doc.querySelector('GSEControl[name="GOOSE3"]')!;
const ln1 = doc.querySelector('LN[lnClass="LGOS"][inst="1"]')!;

expect(
canInstantiateSubscriptionSupervision(
{
sourceControlBlock,
subscriberIedOrLn: ln1,
},
{ allowReplacement: true },
),
).to.be.true;
});
});
11 changes: 10 additions & 1 deletion tLN/supervision/canInstantiateSubscriptionSupervision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ function isControlBlockSupervised(supervision: Supervision): boolean {
* - check whether `Service` element requirements are met
* - check whether the logical node has missing or empty `Val` content
* (iedOrLn is LN)
*
* CAUTION: Using options.allowReplacement and options.newSupervisionLn together
* may result in unexpected behaviour.
* ```
* @returns Whether subscription supervision can be done */
export function canInstantiateSubscriptionSupervision(
Expand All @@ -157,6 +160,7 @@ export function canInstantiateSubscriptionSupervision(
checkEditableSrcRef: true,
checkDuplicateSupervisions: true,
checkMaxSupervisionLimits: true,
allowReplacement: false,
},
): boolean {
if (
Expand All @@ -170,13 +174,18 @@ export function canInstantiateSubscriptionSupervision(
supervision.sourceControlBlock.tagName === "GSEControl"
? "GoCBRef"
: "SvCBRef";
if (holdsValidObjRef(supervision.subscriberIedOrLn, type)) return false;
if (
!options.allowReplacement &&
holdsValidObjRef(supervision.subscriberIedOrLn, type)
)
return false;
} else {
if (!existFirstSupervisionOfType(supervision)) return false;
}

if (
options.checkMaxSupervisionLimits &&
!options.allowReplacement &&
!withinSupervisionLimits(supervision)
)
return false;
Expand Down
9 changes: 9 additions & 0 deletions tLN/supervision/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ export type SupervisionOptions = {
* empty or invalid and can be used. Defaulting to true.
*/
checkMaxSupervisionLimits: boolean;
/**
* Allow replacement of an existing control block reference on an LN.
* If checking IED properties by passing in the first supervision instance
* be sure to set this.
*
* CAUTION: Using options.allowReplacement and options.newSupervisionLn together
* may result in unexpected behaviour.
*/
allowReplacement: boolean;
};

export function type(supervision: Supervision): "GoCBRef" | "SvCBRef" {
Expand Down
22 changes: 11 additions & 11 deletions tLN/supervision/insertSubscriptionSupervisions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe("Functions that inserts supervision to subscription edits", () => {

it("return empty array with unsufficient update edit", () => {
const extRef1 = doc.querySelector(
'IED[name="GOOSE_Subscriber4"] ExtRef[intAddr="Pos.stVal"]'
'IED[name="GOOSE_Subscriber4"] ExtRef[intAddr="Pos.stVal"]',
)!;

const attributes1 = {};
Expand Down Expand Up @@ -147,7 +147,7 @@ describe("Functions that inserts supervision to subscription edits", () => {

it("return array of edits for update subscription edits", () => {
const extRef1 = doc.querySelector(
'IED[name="GOOSE_Subscriber4"] ExtRef[intAddr="Pos.stVal"]'
'IED[name="GOOSE_Subscriber4"] ExtRef[intAddr="Pos.stVal"]',
)!;

const attributes1 = {
Expand All @@ -166,7 +166,7 @@ describe("Functions that inserts supervision to subscription edits", () => {
const update1 = { element: extRef1, attributes: attributes1 };

const extRef2 = doc.querySelector(
'IED[name="GOOSE_Subscriber4"] ExtRef[intAddr="Pos.q"]'
'IED[name="GOOSE_Subscriber4"] ExtRef[intAddr="Pos.q"]',
)!;

const attributes2 = {
Expand Down Expand Up @@ -231,10 +231,10 @@ describe("Functions that inserts supervision to subscription edits", () => {

it("return array for multiple combined update/insert edits to multiple IEDs", () => {
const ied4e1 = doc.querySelector(
'IED[name="GOOSE_Subscriber4"] ExtRef[intAddr="Pos.stVal"]'
'IED[name="GOOSE_Subscriber4"] ExtRef[intAddr="Pos.stVal"]',
)!;
const ied5e1 = doc.querySelector(
'IED[name="GOOSE_Subscriber5"] ExtRef[intAddr="Pos.stVal"]'
'IED[name="GOOSE_Subscriber5"] ExtRef[intAddr="Pos.stVal"]',
)!;
const attributes1 = {
iedName: "Publisher",
Expand Down Expand Up @@ -301,26 +301,26 @@ describe("Functions that inserts supervision to subscription edits", () => {

expect(edits.length).to.equal(6);
expect(
(edits[0].node as Element).querySelector("Val")?.textContent
(edits[0].node as Element).querySelector("Val")?.textContent,
).to.equal("PublisherGOOSE/LLN0.GOOSE3");
expect(
(edits[1].node as Element).querySelector("Val")?.textContent
(edits[1].node as Element).querySelector("Val")?.textContent,
).to.equal("PublisherGOOSE/LLN0.GOOSE3");
expect((edits[1].node as Element).getAttribute("inst")).to.equal("2");
expect(
(edits[2].node as Element).querySelector("Val")?.textContent
(edits[2].node as Element).querySelector("Val")?.textContent,
).to.equal("PublisherGOOSE/LLN0.GOOSE5");
expect((edits[2].node as Element).getAttribute("inst")).to.equal("4");
expect(
(edits[3].node as Element).querySelector("Val")?.textContent
(edits[3].node as Element).querySelector("Val")?.textContent,
).to.equal("PublisherGOOSE/LLN0.GOOSE6");
expect((edits[3].node as Element).getAttribute("inst")).to.equal("6");
expect(
(edits[4].node as Element).querySelector("Val")?.textContent
(edits[4].node as Element).querySelector("Val")?.textContent,
).to.equal("PublisherGOOSE/LLN0.GOOSE5");
expect((edits[4].node as Element).getAttribute("inst")).to.equal("3");
expect(
(edits[5].node as Element).querySelector("Val")?.textContent
(edits[5].node as Element).querySelector("Val")?.textContent,
).to.equal("PublisherGOOSE/LLN0.GOOSE6");
expect((edits[5].node as Element).getAttribute("inst")).to.equal("6");
});
Expand Down
10 changes: 5 additions & 5 deletions tLN/supervision/insertSubscriptionSupervisions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { createSupervisionEdit } from "./createSupervisionEdit.js";
* @returns A sink IED for a given ExtRef insert or `null`*/
function findIED(
edit: Insert,
previousEdits: (Insert | Update)[]
previousEdits: (Insert | Update)[],
): Element | null {
// case 1: Input is already in the SCL and ExtRef is added to it
const inputs = edit.parent as Element;
Expand All @@ -26,7 +26,7 @@ function findIED(

// case 2: Input element is added as part of the subscription as well.
const inputsInsertEdit = previousEdits.find(
(otherEdit) => isInsert(otherEdit) && otherEdit.node === edit.parent
(otherEdit) => isInsert(otherEdit) && otherEdit.node === edit.parent,
) as Insert | undefined;
if (inputsInsertEdit)
return (inputsInsertEdit.parent as Element).closest("IED");
Expand All @@ -36,7 +36,7 @@ function findIED(
}

function uniqueSupervisions(
edits: (Insert | Update)[]
edits: (Insert | Update)[],
): Record<string, Supervision> {
const uniqueSupervisions: Record<string, Supervision> = {};
edits.forEach((edit) => {
Expand Down Expand Up @@ -75,7 +75,7 @@ function uniqueSupervisions(

const controlBlock = findControlBlockBySrcAttributes(
sink!.ownerDocument,
source!
source!,
);
if (!controlBlock) return;
const controlBlockReference = controlBlockObjRef(controlBlock)!;
Expand Down Expand Up @@ -104,7 +104,7 @@ function uniqueSupervisions(
* @returns Edit array adding supervision on top of the subscription
*/
export function insertSubscriptionSupervisions(
edits: (Update | Insert)[]
edits: (Update | Insert)[],
): Insert[] {
/** Global as multiple subscription could be defined for different subscriber IEDs */
const instGenerator = globalLnInstGenerator();
Expand Down
1 change: 1 addition & 0 deletions tLN/supervision/instantiateSubscriptionSupervision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function instantiateSubscriptionSupervision(
checkEditableSrcRef: true,
checkDuplicateSupervisions: true,
checkMaxSupervisionLimits: true,
allowReplacement: false,
},
): Insert | null {
if (!canInstantiateSubscriptionSupervision(supervision, options)) return null;
Expand Down

0 comments on commit 8065496

Please sign in to comment.