Skip to content

Commit a3cadff

Browse files
feat(LN): add describe Inputs
1 parent 627a053 commit a3cadff

File tree

4 files changed

+219
-3
lines changed

4 files changed

+219
-3
lines changed

describe/Inputs.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { expect } from "chai";
2+
3+
import { describeInputs } from "./Inputs.js";
4+
5+
const scl = new DOMParser().parseFromString(
6+
`<SCL
7+
xmlns="http://www.iec.ch/61850/2003/SCL"
8+
>
9+
<LN0 lnClass="LLN0" inst="" >
10+
<Inputs>
11+
<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" />
12+
<ExtRef iedName="ied1" ldInst="ldInst" lnClass="LLN0" doName="Beh" daName="q" serviceType="GOOSE" srcLDInst="ldInst" srcLNClass="LLN0" srcCBName="cbName" />
13+
<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" />
14+
<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" />
15+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
16+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
17+
</Inputs>
18+
</LN0>
19+
<LN prefix="A" lnClass="MMXU" inst="1" >
20+
<Inputs>
21+
<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" />
22+
<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" />
23+
<ExtRef iedName="ied1" ldInst="ldInst" lnClass="LLN0" doName="Beh" daName="q" serviceType="GOOSE" srcLDInst="ldInst" srcPrefix="" srcLNClass="LLN0" srcLNInst="" srcCBName="cbName" />
24+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
25+
<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" />
26+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
27+
</Inputs>
28+
</LN>
29+
<LN prefix="B" lnClass="MMXU" inst="2" >
30+
<Inputs>
31+
<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" />
32+
<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" />
33+
<ExtRef iedName="ied1" ldInst="ldInst" lnClass="LLN0" doName="Beh" daName="q" serviceType="GOOSE" srcLDInst="ldInst" srcPrefix="" srcLNClass="LLN0" srcLNInst="" srcCBName="cbName" />
34+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
35+
<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" />
36+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
37+
</Inputs>
38+
</LN>
39+
</SCL>`,
40+
"application/xml",
41+
);
42+
43+
const baseInputs = scl.querySelector("LN0 > Inputs")!;
44+
const equalInputs = scl.querySelector('LN[lnClass="MMXU"][inst="1"] > Inputs')!;
45+
const diffInputs = scl.querySelector('LN[lnClass="MMXU"][inst="2"] > Inputs')!;
46+
47+
describe("Description for SCL schema element Inputs", () => {
48+
it("returns same description with semantically equal Inputs", () =>
49+
expect(JSON.stringify(describeInputs(baseInputs))).to.equal(
50+
JSON.stringify(describeInputs(equalInputs)),
51+
));
52+
53+
it("returns same description with semantically different Inputs", () =>
54+
expect(JSON.stringify(describeInputs(baseInputs))).to.not.equal(
55+
JSON.stringify(describeInputs(diffInputs)),
56+
));
57+
});

describe/Inputs.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { NamingDescription, describeNaming } from "./Naming.js";
2+
3+
function compareExtRefDescription(
4+
a: ExtRefDescription,
5+
b: ExtRefDescription,
6+
): number {
7+
const stringifiedA = JSON.stringify(a);
8+
const stringifiedB = JSON.stringify(b);
9+
10+
if (stringifiedA < stringifiedB) return -1;
11+
if (stringifiedA > stringifiedB) return 1;
12+
return 0;
13+
}
14+
15+
interface ExtRefDescription {
16+
/** Source IED name attribute */
17+
iedName?: string;
18+
/** Source LDevice inst attribute */
19+
ldInst?: string;
20+
/** Source AnyLn prefix attribute */
21+
prefix?: string;
22+
/** Source AnyLn lnClass attribute */
23+
lnClass?: string;
24+
/** Source AnyLn lnInst attribute */
25+
lnInst?: string;
26+
/** Source data object(s) */
27+
doName?: string;
28+
/** Source data attributes(s) */
29+
daName?: string;
30+
/** ExtRef attribute instAddr attribute */
31+
intAddr?: string;
32+
/** Source control block name attribute */
33+
srcCBName?: string;
34+
/** Source control block parent LDevice inst attribute */
35+
srcLDInst?: string;
36+
/** Source control block parent AnyLn prefix attribute */
37+
srcPrefix?: string;
38+
/** Source control block parent AnyLn lnInst attribute */
39+
srcLNClass?: string;
40+
/** Source control block parent AnyLn inst attribute */
41+
srcLNInst?: string;
42+
/** Source control block type */
43+
serviceType?: "GOOSE" | "Report" | "SMV" | "Poll";
44+
/** Restriction logical node class */
45+
pLN?: string;
46+
/** Restriction data object name(s) */
47+
pDO?: string;
48+
/** Restriction data attribute name(s) */
49+
pDA?: string;
50+
/** Restriction control block type */
51+
pServT?: "GOOSE" | "Report" | "SMV" | "Poll";
52+
}
53+
54+
function describeExtRef(element: Element): ExtRefDescription {
55+
const extRefDesc: ExtRefDescription = {};
56+
57+
const [
58+
iedName,
59+
ldInst,
60+
prefix,
61+
lnClass,
62+
lnInst,
63+
doName,
64+
daName,
65+
intAddr,
66+
srcCBName,
67+
srcLDInst,
68+
srcPrefix,
69+
srcLNClass,
70+
srcLNInst,
71+
serviceType,
72+
pLN,
73+
pDO,
74+
pDA,
75+
pServT,
76+
] = [
77+
"iedName",
78+
"ldInst",
79+
"prefix",
80+
"lnClass",
81+
"lnInst",
82+
"doName",
83+
"daName",
84+
"intAddr",
85+
"srcCBName",
86+
"srcLDInst",
87+
"srcPrefix",
88+
"srcLNClass",
89+
"srcLNInst",
90+
"serviceType",
91+
"pLN",
92+
"pDO",
93+
"pDA",
94+
"pServT",
95+
].map((attr) => element.getAttribute(attr));
96+
97+
if (iedName) extRefDesc.iedName = iedName;
98+
if (ldInst) extRefDesc.ldInst = ldInst;
99+
if (prefix) extRefDesc.prefix = prefix;
100+
if (lnClass) extRefDesc.lnClass = lnClass;
101+
if (lnInst) extRefDesc.lnInst = lnInst;
102+
if (doName) extRefDesc.doName = doName;
103+
if (daName) extRefDesc.daName = daName;
104+
if (lnInst) extRefDesc.lnInst = lnInst;
105+
if (intAddr) extRefDesc.intAddr = intAddr;
106+
if (srcCBName) extRefDesc.srcCBName = srcCBName;
107+
if (srcLDInst) extRefDesc.srcLDInst = srcLDInst;
108+
if (srcPrefix) extRefDesc.srcPrefix = srcPrefix;
109+
if (srcLNClass) extRefDesc.srcLNClass = srcLNClass;
110+
if (srcLNInst) extRefDesc.srcLNInst = srcLNInst;
111+
if (serviceType && ["Report", "SMV", "GOOSE", "Poll"].includes(serviceType))
112+
extRefDesc.serviceType = serviceType as "Report" | "SMV" | "GOOSE" | "Poll";
113+
if (pLN) extRefDesc.pLN = pLN;
114+
if (pDO) extRefDesc.pDO = pDO;
115+
if (pDA) extRefDesc.pDA = pDA;
116+
if (pServT && ["Report", "SMV", "GOOSE", "Poll"].includes(pServT))
117+
extRefDesc.pServT = pServT as "Report" | "SMV" | "GOOSE" | "Poll";
118+
119+
return extRefDesc;
120+
}
121+
122+
export interface InputsDescription extends NamingDescription {
123+
extRefs: ExtRefDescription[];
124+
}
125+
126+
export function describeInputs(element: Element): InputsDescription {
127+
const inputsDesc: InputsDescription = {
128+
...describeNaming(element),
129+
extRefs: [],
130+
};
131+
132+
inputsDesc.extRefs.push(
133+
...Array.from(element.children)
134+
.filter((child) => child.tagName === "ExtRef")
135+
.map((extRef) => describeExtRef(extRef))
136+
.sort(compareExtRefDescription),
137+
);
138+
139+
return inputsDesc;
140+
}

describe/LN.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ const scl = new DOMParser().parseFromString(
1414
<Val>on</Val>
1515
</DAI>
1616
</DOI>
17+
<Inputs>
18+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
19+
</Inputs>
1720
</LN0>
1821
<LN prefix="Meas" lnClass="MMXU" inst="1" lnType="MMXU" >
1922
<DataSet name="baseDataSet" >
@@ -51,7 +54,11 @@ const scl = new DOMParser().parseFromString(
5154
<LN desc="invalidLnTypeDescription" prefix="" lnClass="PTOC" inst="1" lnType="invalidPTOC"/>
5255
</LDevice>
5356
<LDevice inst="lDevice2">
54-
<LN0 lnClass="LLN0" inst="" lnType="LLN02" />
57+
<LN0 lnClass="LLN0" inst="" lnType="LLN02" >
58+
<Inputs>
59+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
60+
</Inputs>
61+
</LN0>
5562
<LN prefix="Meas" lnClass="MMXU" inst="1" lnType="MMXU2" >
5663
<DataSet name="equalDataSet" >
5764
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
@@ -72,6 +79,9 @@ const scl = new DOMParser().parseFromString(
7279
<Val>test</Val>
7380
</DAI>
7481
</DOI>
82+
<Inputs>
83+
<ExtRef intAddr="A.phsA" pLN="MMXU" pDO="A.phsA" pDA="cVal.mag.f" pServT="SMV" />
84+
</Inputs>
7585
</LN0>
7686
<LN prefix="Meas" lnClass="MMXU" inst="1" lnType="MMXU" >
7787
<DOI name="A" >

describe/LN.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "./LNodeType.js";
1111
import { LogControlDescription, describeLogControl } from "./LogControl.js";
1212
import { NamingDescription, describeNaming } from "./Naming.js";
13+
import { InputsDescription, describeInputs } from "./Inputs.js";
1314
import {
1415
ReportControlDescription,
1516
describeReportControl,
@@ -19,6 +20,7 @@ import { describeVal, compareBySGroup } from "./Val.js";
1920
export interface LNDescription extends NamingDescription {
2021
reports: Record<string, ReportControlDescription>;
2122
logControls: Record<string, LogControlDescription>;
23+
inputs?: InputsDescription;
2224
lnType: LNodeTypeDescription;
2325
}
2426

@@ -154,10 +156,17 @@ export function LN(element: Element): LNDescription | undefined {
154156

155157
const lnType = updateValues(lNodeTypeDescriptions, instanceValues(element));
156158

157-
return {
159+
const lnDescription: LNDescription = {
158160
...describeNaming(element),
159-
lnType,
160161
reports: reportControls(element),
161162
logControls: logControls(element),
163+
lnType,
162164
};
165+
166+
const inputs = Array.from(element.children).find(
167+
(child) => child.tagName === "Inputs",
168+
);
169+
if (inputs) lnDescription.inputs = describeInputs(inputs);
170+
171+
return lnDescription;
163172
}

0 commit comments

Comments
 (0)