Skip to content

feat(LN): add describe Inputs #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: describeLogControl
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions describe/Inputs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { expect } from "chai";

import { describeInputs } from "./Inputs.js";

const scl = new DOMParser().parseFromString(
`<SCL
xmlns="http://www.iec.ch/61850/2003/SCL"
>
<LN0 lnClass="LLN0" inst="" >
<Inputs>
<ExtRef iedName="ied1" ldInst="ldInst" lnClass="LLN0" doName="Beh" daName="stVal" serviceType="GOOSE" srcLDInst="ldInst" srcLNClass="LLN0" srcCBName="cbName" pLN="LLN0" pDO="Beh" pDA="stVal" pServT="GOOSE" />
<ExtRef iedName="ied1" ldInst="ldInst" lnClass="LLN0" doName="Beh" daName="q" serviceType="GOOSE" srcLDInst="ldInst" srcLNClass="LLN0" srcCBName="cbName" />
<ExtRef iedName="ied1" ldInst="ldInst" prefix="A" lnClass="MMXU" lnInst="1" doName="A.phsA" daName="cVal.mag.f" serviceType="Report" srcLDInst="ldInst" srcPrefix="A" srcLNClass="MMXU" srcLNInst="1" srcCBName="cbName" />
<ExtRef iedName="ied1" ldInst="ldInst" prefix="A" lnClass="MMXU" lnInst="1" doName="A.phsA" daName="q" serviceType="Report" srcLDInst="ldInst" srcPrefix="A" srcLNClass="MMXU" srcLNInst="1" srcCBName="cbName" />
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
</Inputs>
</LN0>
<LN prefix="A" lnClass="MMXU" inst="1" >
<Inputs>
<ExtRef iedName="ied1" ldInst="ldInst" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" serviceType="GOOSE" srcLDInst="ldInst" srcLNClass="LLN0" srcCBName="cbName" pLN="LLN0" pDO="Beh" pDA="stVal" pServT="GOOSE" />
<ExtRef iedName="ied1" ldInst="ldInst" prefix="A" lnClass="MMXU" lnInst="1" doName="A.phsA" daName="cVal.mag.f" serviceType="Report" srcLDInst="ldInst" srcPrefix="A" srcLNClass="MMXU" srcLNInst="1" srcCBName="cbName" />
<ExtRef iedName="ied1" ldInst="ldInst" lnClass="LLN0" doName="Beh" daName="q" serviceType="GOOSE" srcLDInst="ldInst" srcPrefix="" srcLNClass="LLN0" srcLNInst="" srcCBName="cbName" />
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
<ExtRef iedName="ied1" ldInst="ldInst" prefix="A" lnClass="MMXU" lnInst="1" doName="A.phsA" daName="q" serviceType="Report" srcLDInst="ldInst" srcPrefix="A" srcLNClass="MMXU" srcLNInst="1" srcCBName="cbName" />
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
</Inputs>
</LN>
<LN prefix="B" lnClass="MMXU" inst="2" >
<Inputs>
<ExtRef iedName="ied1" ldInst="ldInst" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" serviceType="GOOSE" srcLDInst="ldInst" srcLNClass="LLN0" srcCBName="cbName" pLN="LLN0" pDO="Beh" pDA="stVal" pServT="GOOSE" />
<ExtRef iedName="ied1" ldInst="ldInst" prefix="A" lnClass="MMXU" lnInst="1" doName="A.phsB" daName="cVal.mag.f" serviceType="Report" srcLDInst="ldInst" srcPrefix="A" srcLNClass="MMXU" srcLNInst="1" srcCBName="cbName" />
<ExtRef iedName="ied1" ldInst="ldInst" lnClass="LLN0" doName="Beh" daName="q" serviceType="GOOSE" srcLDInst="ldInst" srcPrefix="" srcLNClass="LLN0" srcLNInst="" srcCBName="cbName" />
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
<ExtRef iedName="ied1" ldInst="ldInst" prefix="A" lnClass="MMXU" lnInst="1" doName="A.phsB" daName="q" serviceType="Report" srcLDInst="ldInst" srcPrefix="A" srcLNClass="MMXU" srcLNInst="1" srcCBName="cbName" />
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
</Inputs>
</LN>
</SCL>`,
"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)),
));
});
140 changes: 140 additions & 0 deletions describe/Inputs.ts
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 11 additions & 1 deletion describe/LN.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const scl = new DOMParser().parseFromString(
<Val>on</Val>
</DAI>
</DOI>
<Inputs>
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
</Inputs>
</LN0>
<LN prefix="Meas" lnClass="MMXU" inst="1" lnType="MMXU" >
<DataSet name="baseDataSet" >
Expand Down Expand Up @@ -51,7 +54,11 @@ const scl = new DOMParser().parseFromString(
<LN desc="invalidLnTypeDescription" prefix="" lnClass="PTOC" inst="1" lnType="invalidPTOC"/>
</LDevice>
<LDevice inst="lDevice2">
<LN0 lnClass="LLN0" inst="" lnType="LLN02" />
<LN0 lnClass="LLN0" inst="" lnType="LLN02" >
<Inputs>
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
</Inputs>
</LN0>
<LN prefix="Meas" lnClass="MMXU" inst="1" lnType="MMXU2" >
<DataSet name="equalDataSet" >
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
Expand All @@ -72,6 +79,9 @@ const scl = new DOMParser().parseFromString(
<Val>test</Val>
</DAI>
</DOI>
<Inputs>
<ExtRef intAddr="A.phsA" pLN="MMXU" pDO="A.phsA" pDA="cVal.mag.f" pServT="SMV" />
</Inputs>
</LN0>
<LN prefix="Meas" lnClass="MMXU" inst="1" lnType="MMXU" >
<DOI name="A" >
Expand Down
13 changes: 11 additions & 2 deletions describe/LN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -19,6 +20,7 @@ import { describeVal, compareBySGroup } from "./Val.js";
export interface LNDescription extends NamingDescription {
reports: Record<string, ReportControlDescription>;
logControls: Record<string, LogControlDescription>;
inputs?: InputsDescription;
lnType: LNodeTypeDescription;
}

Expand Down Expand Up @@ -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;
}