Skip to content

Commit 7ef0dd8

Browse files
feat: describe LN0
1 parent ef03895 commit 7ef0dd8

13 files changed

+405
-29
lines changed

describe.spec.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,31 @@ const testScl = new DOMParser().parseFromString(
1111
<IED name="IED">
1212
<AccessPoint name="AP1">
1313
<Server>
14-
<LDevice inst="ldInst">
15-
<LN lnClass="XCBR" inst="" lnType="baseXCBR"/>
16-
<LN lnClass="XCBR" inst="" lnType="equalXCBR"/>
17-
<LN lnClass="XCBR" inst="" lnType="diffXCBR"/>
14+
<LDevice inst="ldInst1">
15+
<LN0 lnClass="LLN0" inst="" lnType="baseLLN0"/>
16+
<LN lnClass="XCBR" inst="1" lnType="baseXCBR"/>
17+
<LN lnClass="XCBR" inst="1" lnType="equalXCBR"/>
18+
<LN lnClass="XCBR" inst="1" lnType="diffXCBR"/>
19+
</LDevice>
20+
<LDevice inst="ldInst2">
21+
<LN0 lnClass="LLN0" inst="" lnType="equalLLN0"/>
22+
</LDevice>
23+
<LDevice inst="ldInst3">
24+
<LN0 lnClass="LLN0" inst="" lnType="diffLLN0"/>
1825
</LDevice>
1926
</Server>
2027
</AccessPoint>
2128
</IED>
2229
<DataTypeTemplates>
30+
<LNodeType lnClass="LLN0" id="baseLLN0" >
31+
<DO name="Beh" type="someEqualDOType" />
32+
</LNodeType>
33+
<LNodeType lnClass="LLN0" id="equalLLN0" >
34+
<DO name="Beh" type="someEqualDOType" />
35+
</LNodeType>
36+
<LNodeType lnClass="LLN0" id="diffLLN0" >
37+
<DO name="Beh" type="someDiffDOType" />
38+
</LNodeType>
2339
<LNodeType lnClass="XCBR" id="equalXCBR" >
2440
<DO name="Beh" type="someEqualDOType" />
2541
</LNodeType>
@@ -102,6 +118,10 @@ const baseLN = testScl.querySelector(`LN[lnType="baseXCBR"]`)!;
102118
const equalLN = testScl.querySelector(`LN[lnType="equalXCBR"]`)!;
103119
const diffLN = testScl.querySelector(`LN[lnType="diffXCBR"]`)!;
104120

121+
const baseLN0 = testScl.querySelector(`LDevice[inst="ldInst1"]>LN0`)!;
122+
const equalLN0 = testScl.querySelector(`LDevice[inst="ldInst2"]>LN0`)!;
123+
const diffLN0 = testScl.querySelector(`LDevice[inst="ldInst3"]>LN0`)!;
124+
105125
describe("Describe SCL elements function", () => {
106126
it("returns undefined with missing describe function", () =>
107127
expect(describeSclElement(oneNonSCLElement)).to.be.undefined);
@@ -119,6 +139,16 @@ describe("Describe SCL elements function", () => {
119139
JSON.stringify(describeSclElement(equalEnumType)),
120140
));
121141

142+
it("returns same description with semantically equal LN0's", () =>
143+
expect(JSON.stringify(describeSclElement(baseLN0))).to.equal(
144+
JSON.stringify(describeSclElement(equalLN0)),
145+
));
146+
147+
it("returns different description with unequal LN0 elements", () =>
148+
expect(JSON.stringify(describeSclElement(baseLN0))).to.not.equal(
149+
JSON.stringify(describeSclElement(diffLN0)),
150+
));
151+
122152
it("returns same description with semantically equal LN's", () =>
123153
expect(JSON.stringify(describeSclElement(baseLN))).to.equal(
124154
JSON.stringify(describeSclElement(equalLN)),

describe.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DAType, DATypeDescription } from "./describe/DAType.js";
55
import { DOType, DOTypeDescription } from "./describe/DOType.js";
66
import { LNodeType, LNodeTypeDescription } from "./describe/LNodeType.js";
77
import { LN, LNDescription } from "./describe/LN.js";
8+
import { LN0, LN0Description } from "./describe/LN0.js";
89

910
export type Description =
1011
| PrivateDescription
@@ -14,7 +15,8 @@ export type Description =
1415
| DATypeDescription
1516
| DOTypeDescription
1617
| LNodeTypeDescription
17-
| LNDescription;
18+
| LNDescription
19+
| LN0Description;
1820
const sclElementDescriptors: Partial<
1921
Record<string, (element: Element) => Description | undefined>
2022
> = {
@@ -25,6 +27,7 @@ const sclElementDescriptors: Partial<
2527
DOType,
2628
LNodeType,
2729
LN,
30+
LN0,
2831
};
2932

3033
export function describe(element: Element): Description | undefined {

describe/ControlWithIEDName.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const scl = new DOMParser().parseFromString(
5252
</AccessPoint>
5353
</IED>
5454
</SCL>`,
55-
"application/xml"
55+
"application/xml",
5656
);
5757

5858
const baseGSEControl = scl.querySelector(`*[datSet="baseDataSet"]`)!;
@@ -70,11 +70,11 @@ describe("Description for SCL schema type tControlWithIEDName", () => {
7070

7171
it("returns same description with semantically equal ControlWithIEDName's", () =>
7272
expect(JSON.stringify(describeControlWithIEDName(baseGSEControl))).to.equal(
73-
JSON.stringify(describeControlWithIEDName(equalGSEControl))
73+
JSON.stringify(describeControlWithIEDName(equalGSEControl)),
7474
));
7575

7676
it("returns different description with unequal ControlWithIEDName elements", () =>
7777
expect(
78-
JSON.stringify(describeControlWithIEDName(baseGSEControl))
78+
JSON.stringify(describeControlWithIEDName(baseGSEControl)),
7979
).to.not.equal(JSON.stringify(describeControlWithIEDName(diffGSEControl))));
8080
});

describe/ControlWithIEDName.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function compareIEDNameDescription(a: IEDName, b: IEDName): number {
99
return 0;
1010
}
1111

12-
type IEDName = {
12+
interface IEDName {
1313
/** IEDName attribute apRef*/
1414
apRef?: string;
1515
/** IEDName attribute ldInst*/
@@ -22,7 +22,7 @@ type IEDName = {
2222
lnInst?: string;
2323
/** IEDName child text content */
2424
val?: string;
25-
};
25+
}
2626

2727
function describeIEDName(element: Element): IEDName {
2828
const iedName: IEDName = {};
@@ -55,7 +55,7 @@ export interface ControlWithIEDNameDescription extends ControlDescription {
5555
}
5656

5757
export function describeControlWithIEDName(
58-
element: Element
58+
element: Element,
5959
): ControlWithIEDNameDescription | undefined {
6060
const controlDescription = describeControl(element);
6161
if (!controlDescription) return;
@@ -69,7 +69,7 @@ export function describeControlWithIEDName(
6969
...Array.from(element.children)
7070
.filter((child) => child.tagName === "IEDName")
7171
.map((iedName) => describeIEDName(iedName))
72-
.sort(compareIEDNameDescription)
72+
.sort(compareIEDNameDescription),
7373
);
7474

7575
const confRev = element.getAttribute("confRev");

describe/GSEControl.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const scl = new DOMParser().parseFromString(
5454
</AccessPoint>
5555
</IED>
5656
</SCL>`,
57-
"application/xml"
57+
"application/xml",
5858
);
5959

6060
const baseGSEControl = scl.querySelector(`*[datSet="baseDataSet"]`)!;
@@ -72,11 +72,11 @@ describe("Description for SCL schema type tControlWithIEDName", () => {
7272

7373
it("returns same description with semantically equal GSEControl's", () =>
7474
expect(JSON.stringify(describeGSEControl(baseGSEControl))).to.equal(
75-
JSON.stringify(describeGSEControl(equalGSEControl))
75+
JSON.stringify(describeGSEControl(equalGSEControl)),
7676
));
7777

7878
it("returns different description with unequal GSEControl elements", () =>
7979
expect(JSON.stringify(describeGSEControl(baseGSEControl))).to.not.equal(
80-
JSON.stringify(describeGSEControl(diffGSEControl))
80+
JSON.stringify(describeGSEControl(diffGSEControl)),
8181
));
8282
});

describe/GSEControl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface GSEControlDescription extends ControlWithIEDNameDescription {
1717
}
1818

1919
export function describeGSEControl(
20-
element: Element
20+
element: Element,
2121
): GSEControlDescription | undefined {
2222
const controlWithTriggerOptDesc = describeControlWithIEDName(element);
2323
if (!controlWithTriggerOptDesc) return;
@@ -35,7 +35,7 @@ export function describeGSEControl(
3535
};
3636

3737
const protocol = Array.from(element.children).find(
38-
(child) => child.tagName === "Protocol"
38+
(child) => child.tagName === "Protocol",
3939
);
4040
if (protocol)
4141
gseControlDescription.protocol = { mustUnderstand: true, val: "R-GOOSE" };

describe/LN0.spec.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { expect } from "chai";
2+
import { LN } from "./LN.js";
3+
import { LNodeType, isLNodeTypeDescription } from "./LNodeType.js";
4+
import { LN0 } from "./LN0.js";
5+
6+
const scl = new DOMParser().parseFromString(
7+
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" >
8+
<IED name="IED1">
9+
<AccessPoint name="AP1">
10+
<Server>
11+
<LDevice inst="lDevice1">
12+
<LN0 lnClass="LLN0" inst="" lnType="LLN0">
13+
<DataSet name="baseDataSet" >
14+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
15+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
16+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" daName="stVal" fc="ST" />
17+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" fc="ST" />
18+
</DataSet>
19+
<DOI name="Beh" >
20+
<DAI name="stVal">
21+
<Val>on</Val>
22+
</DAI>
23+
</DOI>
24+
<GSEControl name="gse1" datSet="baseDataSet" />
25+
<GSEControl name="gse2" />
26+
<SampledValueControl name="smv1" datSet="baseDataSet" smpRate="80" nofASDU="1" >
27+
<SmvOpts />
28+
</SampledValueControl>
29+
<SampledValueControl name="smv2" smpRate="80" nofASDU="1" >
30+
<SmvOpts />
31+
</SampledValueControl>
32+
<LogControl name="anotherLog" logName="logName" />
33+
<LogControl name="log" dataSet="baseDataSet" logName="logName" reasonCode="true" logEna="true" intgPd="0" bufTime="0" >
34+
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
35+
</LogControl>
36+
<ReportControl name="report" datSet="baseDataSet" intgPd="0" indexed="true" buffered="true" bufTime="0" confRev="0" >
37+
<TrgOps dchg="false" qchg="false" dupd="false" period="false" gi="false" />
38+
<OptFields seqNum="false" timeStamp="false" dataSet="false" reasonCode="false" dataRef="false" entryID="false" configRef="false" bufOvfl="false" />
39+
<RptEnabled max="0" />
40+
</ReportControl>
41+
<ReportControl name="anotherReport" />
42+
<Log name="someLog" />
43+
<Log name="someOtherLog" />
44+
<Inputs>
45+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
46+
</Inputs>
47+
<SettingControl numOfSGs="4" />
48+
</LN0>
49+
</LDevice>
50+
<LDevice inst="lDevice2">
51+
<LN0 lnClass="LLN0" inst="" lnType="LLN02" >
52+
<DataSet name="equalDataSet" >
53+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
54+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
55+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
56+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
57+
</DataSet>
58+
<GSEControl name="gse2" />
59+
<GSEControl name="gse1" datSet="equalDataSet" />
60+
<SampledValueControl name="smv2" smpRate="80" nofASDU="1" >
61+
<SmvOpts />
62+
</SampledValueControl>
63+
<SampledValueControl name="smv1" datSet="equalDataSet" smpRate="80" nofASDU="1" >
64+
<SmvOpts />
65+
</SampledValueControl>
66+
<LogControl name="log" dataSet="equalDataSet" logName="logName" />
67+
<LogControl name="anotherLog" logName="logName" />
68+
<ReportControl name="anotherReport" />
69+
<ReportControl name="report" datSet="equalDataSet" />
70+
<Log name="someOtherLog" />
71+
<Log name="someLog" />
72+
<Inputs>
73+
<ExtRef intAddr="Beh.t" pLN="LLN0" pDO="Beh" pDA="t" pServT="GOOSE" />
74+
</Inputs>
75+
<SettingControl numOfSGs="4" />
76+
</LN0>
77+
</LDevice>
78+
<LDevice inst="lDevice3">
79+
<LN0 lnClass="LLN0" inst="" lnType="LLN0" >
80+
<DOI name="Beh" >
81+
<DAI name="stVal">
82+
<Val>test</Val>
83+
</DAI>
84+
</DOI>
85+
<Inputs>
86+
<ExtRef intAddr="A.phsA" pLN="MMXU" pDO="A.phsA" pDA="cVal.mag.f" pServT="SMV" />
87+
</Inputs>
88+
</LN0>
89+
</LDevice>
90+
<LDevice inst="lDevice4" >
91+
<LN0 lnClass="LLN0" inst="" lnType="diffLLN0" />
92+
</LDevice>
93+
<LDevice inst="lDevice5" >
94+
<LN0 lnClass="LLN0" />
95+
</LDevice>
96+
<LDevice inst="lDevice6" >
97+
<LN0 lnClass="LLN0" lnType="invalidReference" />
98+
</LDevice>
99+
<LDevice inst="lDevice7" >
100+
<LN0 lnClass="LLN0" lnType="invalidLLN0" />
101+
</LDevice>
102+
</Server>
103+
</AccessPoint>
104+
</IED>
105+
<DataTypeTemplates>
106+
<LNodeType id="invalidLLN0" desc="desc" lnType="lnType" />
107+
<LNodeType id="LLN0" desc="desc" lnClass="LLN0">
108+
<DO name="Beh" type="BehENS"/>
109+
</LNodeType>
110+
<LNodeType id="LLN02" desc="desc" lnClass="LLN0">
111+
<DO name="Beh" type="BehENS2"/>
112+
</LNodeType>
113+
<LNodeType id="diffLLN0" desc="desc" lnClass="LLN0">
114+
<DO name="Beh" type="diffBehENS"/>
115+
</LNodeType>
116+
<DOType cdc="ENS" id="BehENS" >
117+
<DA name="stVal" bType="Enum" type="BehModKind" fc="ST" >
118+
<Val>off</Val>
119+
</DA>
120+
</DOType>
121+
<DOType cdc="ENS" id="BehENS2" >
122+
<DA name="stVal" bType="Enum" type="BehModKind" fc="ST" >
123+
<Val>on</Val>
124+
</DA>
125+
</DOType>
126+
<DOType id="diffBehENS" cdc="ENS" >
127+
<DA name="stVal" bType="Enum" type="BehModKind" fc="ST" >
128+
<Val>on</Val>
129+
</DA>
130+
<DA name="q" bType="Quality" fc="ST" />
131+
</DOType>
132+
<EnumType id="BehModKind" >
133+
<EnumVal ord="1">on</EnumVal>
134+
<EnumVal ord="3">test</EnumVal>
135+
<EnumVal ord="5">off</EnumVal>
136+
</EnumType>
137+
<EnumType id="diffBehModKind" >
138+
<EnumVal ord="1">on</EnumVal>
139+
<EnumVal ord="3">test</EnumVal>
140+
</EnumType>
141+
</DataTypeTemplates>
142+
</SCL>`,
143+
"application/xml",
144+
);
145+
146+
const missingLnType = scl.querySelector('LDevice[inst="lDevice5"] > LN0')!;
147+
const invalidLnType = scl.querySelector('LDevice[inst="lDevice6"] > LN0')!;
148+
const invalidLnTypeDescription = scl.querySelector(
149+
'LDevice[inst="lDevice7"] > LN0',
150+
)!;
151+
152+
const baseLLN0 = scl.querySelector(`LDevice[inst="lDevice1"] > LN0`)!;
153+
const equalLLN0 = scl.querySelector('LDevice[inst="lDevice2"] > LN0')!;
154+
const diffLLN0 = scl.querySelector('LDevice[inst="lDevice3"] > LN0')!;
155+
const diffEnumType = scl.querySelector('LDevice[inst="lDevice4"] > LN0')!;
156+
157+
describe("Description for SCL schema type LN0", () => {
158+
it("returns undefined with missing lnType attribute", () =>
159+
expect(LN0(missingLnType)).to.be.undefined);
160+
161+
it("returns undefined with invalid lnType attribute", () =>
162+
expect(LN0(invalidLnType)).to.be.undefined);
163+
164+
it("returns undefined with invalid LNodeType description", () =>
165+
expect(LN0(invalidLnTypeDescription)).to.be.undefined);
166+
167+
it("return logical node structure in lnType key", () =>
168+
expect(LN0(baseLLN0)?.lnType).to.satisfy(isLNodeTypeDescription));
169+
170+
it("returns same description with semantically equal LN0's", () => {
171+
expect(JSON.stringify(LN0(baseLLN0))).to.equal(
172+
JSON.stringify(LN0(equalLLN0)),
173+
);
174+
});
175+
176+
it("returns different description with unequal LN0 elements", () => {
177+
expect(JSON.stringify(LN0(baseLLN0))).to.not.equal(
178+
JSON.stringify(LN0(diffLLN0)),
179+
);
180+
expect(JSON.stringify(LN0(baseLLN0))).to.not.equal(
181+
JSON.stringify(LN0(diffEnumType)),
182+
);
183+
});
184+
});

0 commit comments

Comments
 (0)