From a3cadff739164df36807b47441618c687d156de3 Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Fri, 20 Oct 2023 19:17:40 +0200 Subject: [PATCH] feat(LN): add describe Inputs --- describe/Inputs.spec.ts | 57 ++++++++++++++++ describe/Inputs.ts | 140 ++++++++++++++++++++++++++++++++++++++++ describe/LN.spec.ts | 12 +++- describe/LN.ts | 13 +++- 4 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 describe/Inputs.spec.ts create mode 100644 describe/Inputs.ts diff --git a/describe/Inputs.spec.ts b/describe/Inputs.spec.ts new file mode 100644 index 00000000..7c37965e --- /dev/null +++ b/describe/Inputs.spec.ts @@ -0,0 +1,57 @@ +import { expect } from "chai"; + +import { describeInputs } from "./Inputs.js"; + +const scl = new DOMParser().parseFromString( + ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + "application/xml", +); + +const baseInputs = scl.querySelector("LN0 > Inputs")!; +const equalInputs = scl.querySelector('LN[lnClass="MMXU"][inst="1"] > Inputs')!; +const diffInputs = scl.querySelector('LN[lnClass="MMXU"][inst="2"] > Inputs')!; + +describe("Description for SCL schema element Inputs", () => { + it("returns same description with semantically equal Inputs", () => + expect(JSON.stringify(describeInputs(baseInputs))).to.equal( + JSON.stringify(describeInputs(equalInputs)), + )); + + it("returns same description with semantically different Inputs", () => + expect(JSON.stringify(describeInputs(baseInputs))).to.not.equal( + JSON.stringify(describeInputs(diffInputs)), + )); +}); diff --git a/describe/Inputs.ts b/describe/Inputs.ts new file mode 100644 index 00000000..0770e09c --- /dev/null +++ b/describe/Inputs.ts @@ -0,0 +1,140 @@ +import { NamingDescription, describeNaming } from "./Naming.js"; + +function compareExtRefDescription( + a: ExtRefDescription, + b: ExtRefDescription, +): number { + const stringifiedA = JSON.stringify(a); + const stringifiedB = JSON.stringify(b); + + if (stringifiedA < stringifiedB) return -1; + if (stringifiedA > stringifiedB) return 1; + return 0; +} + +interface ExtRefDescription { + /** Source IED name attribute */ + iedName?: string; + /** Source LDevice inst attribute */ + ldInst?: string; + /** Source AnyLn prefix attribute */ + prefix?: string; + /** Source AnyLn lnClass attribute */ + lnClass?: string; + /** Source AnyLn lnInst attribute */ + lnInst?: string; + /** Source data object(s) */ + doName?: string; + /** Source data attributes(s) */ + daName?: string; + /** ExtRef attribute instAddr attribute */ + intAddr?: string; + /** Source control block name attribute */ + srcCBName?: string; + /** Source control block parent LDevice inst attribute */ + srcLDInst?: string; + /** Source control block parent AnyLn prefix attribute */ + srcPrefix?: string; + /** Source control block parent AnyLn lnInst attribute */ + srcLNClass?: string; + /** Source control block parent AnyLn inst attribute */ + srcLNInst?: string; + /** Source control block type */ + serviceType?: "GOOSE" | "Report" | "SMV" | "Poll"; + /** Restriction logical node class */ + pLN?: string; + /** Restriction data object name(s) */ + pDO?: string; + /** Restriction data attribute name(s) */ + pDA?: string; + /** Restriction control block type */ + pServT?: "GOOSE" | "Report" | "SMV" | "Poll"; +} + +function describeExtRef(element: Element): ExtRefDescription { + const extRefDesc: ExtRefDescription = {}; + + const [ + iedName, + ldInst, + prefix, + lnClass, + lnInst, + doName, + daName, + intAddr, + srcCBName, + srcLDInst, + srcPrefix, + srcLNClass, + srcLNInst, + serviceType, + pLN, + pDO, + pDA, + pServT, + ] = [ + "iedName", + "ldInst", + "prefix", + "lnClass", + "lnInst", + "doName", + "daName", + "intAddr", + "srcCBName", + "srcLDInst", + "srcPrefix", + "srcLNClass", + "srcLNInst", + "serviceType", + "pLN", + "pDO", + "pDA", + "pServT", + ].map((attr) => element.getAttribute(attr)); + + if (iedName) extRefDesc.iedName = iedName; + if (ldInst) extRefDesc.ldInst = ldInst; + if (prefix) extRefDesc.prefix = prefix; + if (lnClass) extRefDesc.lnClass = lnClass; + if (lnInst) extRefDesc.lnInst = lnInst; + if (doName) extRefDesc.doName = doName; + if (daName) extRefDesc.daName = daName; + if (lnInst) extRefDesc.lnInst = lnInst; + if (intAddr) extRefDesc.intAddr = intAddr; + if (srcCBName) extRefDesc.srcCBName = srcCBName; + if (srcLDInst) extRefDesc.srcLDInst = srcLDInst; + if (srcPrefix) extRefDesc.srcPrefix = srcPrefix; + if (srcLNClass) extRefDesc.srcLNClass = srcLNClass; + if (srcLNInst) extRefDesc.srcLNInst = srcLNInst; + if (serviceType && ["Report", "SMV", "GOOSE", "Poll"].includes(serviceType)) + extRefDesc.serviceType = serviceType as "Report" | "SMV" | "GOOSE" | "Poll"; + if (pLN) extRefDesc.pLN = pLN; + if (pDO) extRefDesc.pDO = pDO; + if (pDA) extRefDesc.pDA = pDA; + if (pServT && ["Report", "SMV", "GOOSE", "Poll"].includes(pServT)) + extRefDesc.pServT = pServT as "Report" | "SMV" | "GOOSE" | "Poll"; + + return extRefDesc; +} + +export interface InputsDescription extends NamingDescription { + extRefs: ExtRefDescription[]; +} + +export function describeInputs(element: Element): InputsDescription { + const inputsDesc: InputsDescription = { + ...describeNaming(element), + extRefs: [], + }; + + inputsDesc.extRefs.push( + ...Array.from(element.children) + .filter((child) => child.tagName === "ExtRef") + .map((extRef) => describeExtRef(extRef)) + .sort(compareExtRefDescription), + ); + + return inputsDesc; +} diff --git a/describe/LN.spec.ts b/describe/LN.spec.ts index 7aafb19c..9bb8fd8b 100644 --- a/describe/LN.spec.ts +++ b/describe/LN.spec.ts @@ -14,6 +14,9 @@ const scl = new DOMParser().parseFromString( on + + + @@ -51,7 +54,11 @@ const scl = new DOMParser().parseFromString( - + + + + + @@ -72,6 +79,9 @@ const scl = new DOMParser().parseFromString( test + + + diff --git a/describe/LN.ts b/describe/LN.ts index 459e7bf6..acdb4c25 100644 --- a/describe/LN.ts +++ b/describe/LN.ts @@ -10,6 +10,7 @@ import { } from "./LNodeType.js"; import { LogControlDescription, describeLogControl } from "./LogControl.js"; import { NamingDescription, describeNaming } from "./Naming.js"; +import { InputsDescription, describeInputs } from "./Inputs.js"; import { ReportControlDescription, describeReportControl, @@ -19,6 +20,7 @@ import { describeVal, compareBySGroup } from "./Val.js"; export interface LNDescription extends NamingDescription { reports: Record; logControls: Record; + inputs?: InputsDescription; lnType: LNodeTypeDescription; } @@ -154,10 +156,17 @@ export function LN(element: Element): LNDescription | undefined { const lnType = updateValues(lNodeTypeDescriptions, instanceValues(element)); - return { + const lnDescription: LNDescription = { ...describeNaming(element), - lnType, reports: reportControls(element), logControls: logControls(element), + lnType, }; + + const inputs = Array.from(element.children).find( + (child) => child.tagName === "Inputs", + ); + if (inputs) lnDescription.inputs = describeInputs(inputs); + + return lnDescription; }