Skip to content

Commit 0d38f64

Browse files
JakobVogelsangJakob Vogelsang
authored andcommitted
feat(tLN): instantiate subscription supervision
1 parent 5449b17 commit 0d38f64

11 files changed

+1717
-42
lines changed

index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,13 @@ export {
6464
export { sourceControlBlock } from "./tExtRef/sourceControlBlock.js";
6565
export { isSubscribed } from "./tExtRef/isSubscribed.js";
6666

67-
export { Supervision } from "./tLN/canInstantiateSubscriptionSupervision.js";
68-
export { canInstantiateSubscriptionSupervision } from "./tLN/canInstantiateSubscriptionSupervision.js";
67+
export {
68+
Supervision,
69+
SupervisionOptions,
70+
} from "./tLN/supervision/foundation.js";
71+
export { canInstantiateSubscriptionSupervision } from "./tLN/supervision/canInstantiateSubscriptionSupervision.js";
72+
export { instantiateSubscriptionSupervision } from "./tLN/supervision/instantiateSubscriptionSupervision.js";
73+
export { insertSubscriptionSupervisions } from "./tLN/supervision/insertSubscriptionSupervisions.js";
6974

7075
export { maxAttributes, canAddFCDA } from "./tFCDA/canAddFCDA.js";
7176
export { fcdaBaseTypes } from "./tFCDA/fcdaBaseTypes.js";

tLN/canInstantiateSubscriptionSupervision.spec.ts renamed to tLN/supervision/canInstantiateSubscriptionSupervision.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from "chai";
22

3-
import { canInstantiateSubscriptionSupervision } from "./canInstantiateSubscriptionSupervision";
3+
import { canInstantiateSubscriptionSupervision } from "./canInstantiateSubscriptionSupervision.js";
44

55
export const doc = new DOMParser().parseFromString(
66
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007" revision="B" release="4">
@@ -234,6 +234,8 @@ describe("Function that checks whether subscription supervision can be instantia
234234
subscriberIedOrLn,
235235
},
236236
{
237+
newSupervisionLn: false,
238+
fixedLnInst: -1,
237239
checkDuplicateSupervisions: false,
238240
checkEditableSrcRef: true,
239241
checkMaxSupervisionLimits: true,
@@ -354,6 +356,8 @@ describe("Function that checks whether subscription supervision can be instantia
354356
subscriberIedOrLn,
355357
},
356358
{
359+
newSupervisionLn: false,
360+
fixedLnInst: -1,
357361
checkDuplicateSupervisions: true,
358362
checkEditableSrcRef: false,
359363
checkMaxSupervisionLimits: false,
@@ -471,6 +475,8 @@ describe("Function that checks whether subscription supervision can be instantia
471475
subscriberIedOrLn,
472476
},
473477
{
478+
newSupervisionLn: false,
479+
fixedLnInst: -1,
474480
checkDuplicateSupervisions: true,
475481
checkEditableSrcRef: true,
476482
checkMaxSupervisionLimits: false,

tLN/canInstantiateSubscriptionSupervision.ts renamed to tLN/supervision/canInstantiateSubscriptionSupervision.ts

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,11 @@
1-
import { controlBlockObjRef } from "../tControl/controlBlockObjRef.js";
2-
3-
export type Supervision = {
4-
/** Pointer to the supervision location. This can be either a subscriber IED
5-
* or the logical node (`LGOS`/`LSVS`) itself */
6-
subscriberIedOrLn: Element;
7-
/** The control block to be supervised */
8-
sourceControlBlock: Element;
9-
};
10-
11-
export type SupervisionOptions = {
12-
/** Whether to check for `valKind`/`valImport`. Defaulting to true */
13-
checkEditableSrcRef: boolean;
14-
/**
15-
* Whether the given control block is already supervised in the subscriber
16-
* IED. Defaulting to true.
17-
*/
18-
checkDuplicateSupervisions: boolean;
19-
/**
20-
* Whether a new supervision would exceed the limits set in the `Services`
21-
* element or whether the subscriber LN already hosts a valid supervision.
22-
* Defaulting to true.
23-
*/
24-
checkMaxSupervisionLimits: boolean;
25-
};
26-
27-
function supervisionLnClass(supervision: Supervision): "LGOS" | "LSVS" {
28-
const serviceType = supervision.sourceControlBlock.tagName;
29-
return serviceType === "GSEControl" ? "LGOS" : "LSVS";
30-
}
1+
import { controlBlockObjRef } from "../../tControl/controlBlockObjRef.js";
312

32-
function type(supervision: Supervision): "GoCBRef" | "SvCBRef" {
33-
const serviceType = supervision.sourceControlBlock.tagName;
34-
return serviceType === "GSEControl" ? "GoCBRef" : "SvCBRef";
35-
}
3+
import {
4+
Supervision,
5+
SupervisionOptions,
6+
supervisionLnClass,
7+
type,
8+
} from "./foundation.js";
369

3710
/** @returns Whether a supervision LN holds a valid control block object ref */
3811
function holdsValidObjRef(ln: Element, type: "GoCBRef" | "SvCBRef"): boolean {
@@ -52,7 +25,7 @@ function holdsValidObjRef(ln: Element, type: "GoCBRef" | "SvCBRef"): boolean {
5225
}
5326

5427
/** @returns Whether `Services` element requirement is met */
55-
function exceedSupervisionLimits(supervision: Supervision): boolean {
28+
function withinSupervisionLimits(supervision: Supervision): boolean {
5629
const subscriberIed =
5730
supervision.subscriberIedOrLn.tagName === "IED"
5831
? supervision.subscriberIedOrLn
@@ -64,16 +37,18 @@ function exceedSupervisionLimits(supervision: Supervision): boolean {
6437
?.getAttribute(`${lnClass === "LGOS" ? "maxGo" : "maxSv"}`);
6538
if (!max || isNaN(parseInt(max, 10))) return false;
6639

67-
const existingSupervisionLogicalNode = Array.from(
40+
const existingSupervisionLNs = Array.from(
6841
subscriberIed.querySelectorAll(`LN[lnClass="${lnClass}"]`),
6942
);
7043

71-
const availableSupervisorSpots = existingSupervisionLogicalNode.filter(
44+
if (existingSupervisionLNs.length < parseInt(max, 10)) return true;
45+
46+
const availableSupervisorSpots = existingSupervisionLNs.filter(
7247
(ln) => !holdsValidObjRef(ln, type(supervision)),
7348
);
7449

7550
return (
76-
existingSupervisionLogicalNode.length <= parseInt(max, 10) &&
51+
existingSupervisionLNs.length === parseInt(max, 10) &&
7752
availableSupervisorSpots.length > 0
7853
);
7954
}
@@ -177,6 +152,8 @@ function isControlBlockSupervised(supervision: Supervision): boolean {
177152
export function canInstantiateSubscriptionSupervision(
178153
supervision: Supervision,
179154
options: SupervisionOptions = {
155+
newSupervisionLn: false,
156+
fixedLnInst: -1,
180157
checkEditableSrcRef: true,
181158
checkDuplicateSupervisions: true,
182159
checkMaxSupervisionLimits: true,
@@ -200,7 +177,7 @@ export function canInstantiateSubscriptionSupervision(
200177

201178
if (
202179
options.checkMaxSupervisionLimits &&
203-
!exceedSupervisionLimits(supervision)
180+
!withinSupervisionLimits(supervision)
204181
)
205182
return false;
206183

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import { Insert, createElement } from "../../foundation/utils.js";
2+
3+
import { getReference } from "../../tBaseElement/getReference.js";
4+
5+
import { controlBlockObjRef } from "../../tControl/controlBlockObjRef.js";
6+
7+
import { Supervision, supervisionLnClass, type } from "./foundation.js";
8+
9+
function isFixedInst(
10+
options: CreateSupervisionOptions,
11+
): options is SupervisionWithFixedInst {
12+
return "fixedInst" in options;
13+
}
14+
15+
function isNewSupervisionLn(
16+
options: CreateSupervisionOptions,
17+
): options is NewSupervisionLN {
18+
return "newSupervisionLn" in options;
19+
}
20+
21+
type SupervisionWithDynamicInst = {
22+
usedSupervisions?: Set<Element>;
23+
instGenerator: (supervision: Supervision) => string | undefined;
24+
};
25+
26+
type SupervisionWithFixedInst = {
27+
usedSupervisions?: Set<Element>;
28+
fixedInst: string;
29+
};
30+
31+
type NewSupervisionLN = {
32+
usedSupervisions?: Set<Element>;
33+
newSupervisionLn: boolean;
34+
fixedInst?: string;
35+
instGenerator: (supervision: Supervision) => string | undefined;
36+
};
37+
38+
type CreateSupervisionOptions =
39+
| NewSupervisionLN
40+
| SupervisionWithFixedInst
41+
| SupervisionWithDynamicInst;
42+
43+
function createSupervisionDaiElement(input: {
44+
doc: XMLDocument;
45+
cbRefType: string;
46+
controlBlockReference: string;
47+
}): Element {
48+
const dai = createElement(input.doc, "DAI", { name: "setSrcRef" });
49+
const val = createElement(input.doc, "Val", {});
50+
51+
dai.appendChild(val);
52+
val.textContent = input.controlBlockReference;
53+
54+
return dai;
55+
}
56+
57+
function createSupervisionDoiElement(input: {
58+
doc: XMLDocument;
59+
cbRefType: string;
60+
controlBlockReference: string;
61+
}): Element {
62+
const doi = createElement(input.doc, "DOI", { name: input.cbRefType });
63+
const dai = createElement(input.doc, "DAI", { name: "setSrcRef" });
64+
const val = createElement(input.doc, "Val", {});
65+
66+
doi.appendChild(dai);
67+
dai.appendChild(val);
68+
val.textContent = input.controlBlockReference;
69+
70+
return doi;
71+
}
72+
73+
function cdRefType(logicalNode: Element): string {
74+
const lnClass = logicalNode.getAttribute("lnClass");
75+
const cbRefType = lnClass === "LGOS" ? "GoCBRef" : "SvCBRef";
76+
77+
return cbRefType;
78+
}
79+
80+
function availableSupervisionLn(
81+
supervision: Supervision,
82+
usedSupervisions?: Set<Element>,
83+
): Element | null {
84+
const lnClass = supervisionLnClass(supervision);
85+
const cbRefType = type(supervision);
86+
87+
return (
88+
Array.from(
89+
supervision.subscriberIedOrLn.querySelectorAll(
90+
`:scope > AccessPoint > Server > LDevice > LN[lnClass="${lnClass}"]`,
91+
),
92+
)
93+
// filter already used available logical nodes
94+
.filter((ln) => !usedSupervisions?.has(ln))
95+
.find((ln) => {
96+
return (
97+
ln.querySelector(
98+
`:scope > DOI[name="${cbRefType}"] > DAI[name="setSrcRef"] > Val`,
99+
) === null ||
100+
ln.querySelector(
101+
`:scope > DOI[name="${cbRefType}"] > DAI[name="setSrcRef"] > Val`,
102+
)?.textContent === ""
103+
);
104+
}) ?? null
105+
);
106+
}
107+
108+
function createSupervisionLogicalNode(
109+
supervision: Supervision,
110+
controlBlockReference: string,
111+
inst: string,
112+
) {
113+
const subscriberIed = supervision.subscriberIedOrLn;
114+
115+
const lnClass = supervisionLnClass(supervision);
116+
117+
const formLn = subscriberIed.querySelector(`LN[lnClass="${lnClass}"]`)!;
118+
const parent = formLn.parentElement!;
119+
120+
const lnType = formLn.getAttribute("lnType")!;
121+
const prefix = formLn.getAttribute("prefix");
122+
123+
const ln = createElement(subscriberIed.ownerDocument, "LN", {
124+
prefix,
125+
lnClass,
126+
lnType,
127+
inst,
128+
});
129+
const openScdTag = createElement(subscriberIed.ownerDocument, "Private", {
130+
type: "OpenSCD.create",
131+
});
132+
ln.appendChild(openScdTag);
133+
134+
const lastSupervisionLn = parent.querySelector(
135+
`:scope > LN[lnClass="${lnClass}"]:last-child`,
136+
);
137+
const reference = lastSupervisionLn
138+
? lastSupervisionLn.nextElementSibling
139+
: getReference(parent, "LN");
140+
141+
const doc = ln.ownerDocument;
142+
const cbRefType = cdRefType(ln);
143+
144+
ln.appendChild(
145+
createSupervisionDoiElement({
146+
doc,
147+
cbRefType,
148+
controlBlockReference,
149+
}),
150+
);
151+
152+
return { parent, node: ln, reference };
153+
}
154+
155+
function updateSupervisionLogicalNode(
156+
controlBlockReference: string,
157+
logicalNode: Element,
158+
): Insert {
159+
const doc = logicalNode.ownerDocument;
160+
const cbRefType = cdRefType(logicalNode);
161+
162+
const createSupervisionElementInput = {
163+
doc,
164+
cbRefType,
165+
controlBlockReference,
166+
};
167+
168+
const doi = logicalNode.querySelector(`:scope > DOI[name="${cbRefType}"]`);
169+
if (!doi)
170+
return {
171+
parent: logicalNode,
172+
node: createSupervisionDoiElement(createSupervisionElementInput),
173+
reference: getReference(logicalNode, "DOI"),
174+
};
175+
176+
const dai = logicalNode.querySelector(
177+
`:scope > DOI[name="${cbRefType}"] > DAI[name="setSrcRef"]`,
178+
);
179+
if (!dai)
180+
return {
181+
parent: doi,
182+
node: createSupervisionDaiElement(createSupervisionElementInput),
183+
reference: getReference(doi, "DAI"),
184+
};
185+
186+
const val = logicalNode.querySelector(
187+
`:scope > DOI[name="${cbRefType}"] > DAI[name="setSrcRef"] > Val`,
188+
);
189+
if (!val) {
190+
const newVal = createElement(doc, "Val", {});
191+
192+
dai.appendChild(newVal);
193+
newVal.textContent = controlBlockReference;
194+
195+
return { parent: dai, node: newVal, reference: getReference(dai, "Val") };
196+
}
197+
198+
const cbRef = document.createTextNode(controlBlockReference);
199+
return { parent: val, node: cbRef, reference: null };
200+
}
201+
202+
/** @returns Insert edit on unused supervision logical node or new supervision logical node */
203+
export function createSupervisionEdit(
204+
supervision: Supervision,
205+
options: CreateSupervisionOptions,
206+
): Insert | null {
207+
const sourceControlBlock = supervision.sourceControlBlock;
208+
const controlBlockReference = controlBlockObjRef(sourceControlBlock);
209+
if (!controlBlockReference) return null;
210+
211+
if (supervision.subscriberIedOrLn.tagName === "LN")
212+
return updateSupervisionLogicalNode(
213+
controlBlockReference,
214+
supervision.subscriberIedOrLn,
215+
);
216+
217+
if (isNewSupervisionLn(options) && options.newSupervisionLn) {
218+
const inst = options.fixedInst
219+
? options.fixedInst
220+
: options.instGenerator(supervision);
221+
222+
return createSupervisionLogicalNode(
223+
supervision,
224+
controlBlockReference,
225+
inst!,
226+
);
227+
}
228+
229+
const unusedSupervisionLogicalNode = availableSupervisionLn(
230+
supervision,
231+
options?.usedSupervisions,
232+
);
233+
if (!unusedSupervisionLogicalNode) {
234+
const inst = isFixedInst(options)
235+
? options.fixedInst
236+
: options.instGenerator(supervision);
237+
238+
return createSupervisionLogicalNode(
239+
supervision,
240+
controlBlockReference,
241+
inst!,
242+
);
243+
}
244+
245+
options.usedSupervisions?.add(unusedSupervisionLogicalNode);
246+
return updateSupervisionLogicalNode(
247+
controlBlockReference,
248+
unusedSupervisionLogicalNode,
249+
);
250+
}

0 commit comments

Comments
 (0)