Skip to content

Commit 03045b3

Browse files
feat: add describe LN0
1 parent 4ef540d commit 03045b3

File tree

6 files changed

+371
-5
lines changed

6 files changed

+371
-5
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
@@ -13,7 +14,8 @@ export type Description =
1314
| DATypeDescription
1415
| DOTypeDescription
1516
| LNodeTypeDescription
16-
| LNDescription;
17+
| LNDescription
18+
| LN0Description;
1719
const sclElementDescriptors: Partial<
1820
Record<string, (element: Element) => Description | undefined>
1921
> = {
@@ -24,6 +26,7 @@ const sclElementDescriptors: Partial<
2426
DOType,
2527
LNodeType,
2628
LN,
29+
LN0,
2730
};
2831

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

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+
});

describe/LN0.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { sortRecord } from "../utils.js";
2+
3+
import { GSEControlDescription, describeGSEControl } from "./GSEControl.js";
4+
import { LN, LNDescription } from "./LN.js";
5+
import {
6+
SampledValueControlDescription,
7+
describeSampledValueControl,
8+
} from "./SampledValueControl.js";
9+
import {
10+
SettingControlDescription,
11+
describeSettingControl,
12+
} from "./SettingControl.js";
13+
14+
export interface LN0Description extends LNDescription {
15+
gseControls: Record<string, GSEControlDescription>;
16+
smvControls: Record<string, SampledValueControlDescription>;
17+
settingControl?: SettingControlDescription;
18+
}
19+
20+
function gseControls(element: Element): Record<string, GSEControlDescription> {
21+
const unsortedGSEControls: Record<string, GSEControlDescription> = {};
22+
23+
Array.from(element.children)
24+
.filter((child) => child.tagName === "GSEControl")
25+
.forEach((gseControl) => {
26+
const name = gseControl.getAttribute("name");
27+
const gseControlDescription = describeGSEControl(gseControl);
28+
if (name && !unsortedGSEControls[name] && gseControlDescription)
29+
unsortedGSEControls[name] = gseControlDescription;
30+
});
31+
32+
return sortRecord(unsortedGSEControls);
33+
}
34+
35+
function smvControls(
36+
element: Element
37+
): Record<string, SampledValueControlDescription> {
38+
const unsortedSampledValueControls: Record<
39+
string,
40+
SampledValueControlDescription
41+
> = {};
42+
43+
Array.from(element.children)
44+
.filter((child) => child.tagName === "SampledValueControl")
45+
.forEach((smvControl) => {
46+
const name = smvControl.getAttribute("name");
47+
const smvControlDescription = describeSampledValueControl(smvControl);
48+
if (name && !unsortedSampledValueControls[name] && smvControlDescription)
49+
unsortedSampledValueControls[name] = smvControlDescription;
50+
});
51+
52+
return sortRecord(unsortedSampledValueControls);
53+
}
54+
55+
export function LN0(element: Element): LN0Description | undefined {
56+
const lnDescription = LN(element);
57+
if (!lnDescription) return;
58+
59+
const ln0Description: LN0Description = {
60+
...lnDescription,
61+
gseControls: gseControls(element),
62+
smvControls: smvControls(element),
63+
};
64+
65+
const settingControl = element.querySelector(":scope > SettingControl");
66+
if (settingControl && describeSettingControl(settingControl))
67+
ln0Description.settingControl = describeSettingControl(settingControl);
68+
69+
return ln0Description;
70+
}

describe/SettingControl.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { expect } from "chai";
2+
3+
import { describeSettingControl } from "./SettingControl.js";
4+
5+
const scl = new DOMParser().parseFromString(
6+
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL">
7+
<LN0 desc="invalid" lnClass="LLN0" >
8+
<SettingControl />
9+
</LN0>
10+
<LN0 desc="diff" lnClass="LLN0" >
11+
<SettingControl numOfSGs="2" />
12+
</LN0>
13+
<LN0 desc="base" lnClass="LLN0" >
14+
<SettingControl numOfSGs="2" actSG="2" resvTms="345" />
15+
</LN0>
16+
<LN0 desc="equal" lnClass="LLN0" >
17+
<SettingControl numOfSGs="2" actSG="2" resvTms="345" />
18+
</LN0>
19+
</SCL>`,
20+
"application/xml"
21+
);
22+
23+
const baseSettingControl = scl.querySelector(
24+
'LN0[desc="base"] > SettingControl'
25+
)!;
26+
const equalSettingControl = scl.querySelector(
27+
'LN0[desc="equal"] > SettingControl'
28+
)!;
29+
const diffSettingControl = scl.querySelector(
30+
'LN0[desc="diff"] > SettingControl'
31+
)!;
32+
const invalidSettingControl = scl.querySelector(
33+
'LN0[desc="invalid"] > SettingControl'
34+
)!;
35+
36+
describe("Describes the SCL element SettingControl", () => {
37+
it("returns undefined with missing numOfSGs ", () =>
38+
expect(describeSettingControl(invalidSettingControl)).to.be.undefined);
39+
40+
it("returns equal description with semantically equal SettingControl element", () =>
41+
expect(JSON.stringify(describeSettingControl(baseSettingControl))).to.equal(
42+
JSON.stringify(describeSettingControl(equalSettingControl))
43+
));
44+
45+
it("returns different description with semantically different SettingControl element", () => {
46+
expect(
47+
JSON.stringify(describeSettingControl(baseSettingControl))
48+
).to.not.equal(JSON.stringify(describeSettingControl(diffSettingControl)));
49+
});
50+
});

0 commit comments

Comments
 (0)