Skip to content

Commit 45cd21d

Browse files
feat: describe GSEControl
1 parent 57b5474 commit 45cd21d

File tree

4 files changed

+286
-0
lines changed

4 files changed

+286
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { expect } from "chai";
2+
3+
import { describeControlWithIEDName } from "./ControlWithIEDName.js";
4+
5+
const scl = new DOMParser().parseFromString(
6+
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" >
7+
<IED name="IED1">
8+
<AccessPoint name="AP1">
9+
<Server>
10+
<LDevice inst="lDevice">
11+
<LN0 lnClass="LLN0" inst="" lnType="LLN0">
12+
<DataSet name="baseDataSet" >
13+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
14+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
15+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" daName="stVal" fc="ST" />
16+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" fc="ST" />
17+
</DataSet>
18+
<DataSet name="equalDataSet" >
19+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
20+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
21+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
22+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
23+
</DataSet>
24+
<DataSet name="diffDataSet" >
25+
<Private type="private" />
26+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
27+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
28+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
29+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
30+
</DataSet>
31+
<DataSet name="invalidDataSet" >
32+
<FCDA ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
33+
</DataSet>
34+
<GSEControl name="gseControl1" datSet="baseDataSet" confRev="1" >
35+
<IEDName apRef="AP1" ldInst="ldInst" prefix="CB" lnClass="CSWI" lnInst="1">IED3</IEDName>
36+
<IEDName >IED1</IEDName>
37+
<IEDName apRef="AP1" ldInst="ldInst" lnClass="LLN0">IED2</IEDName>
38+
<IEDName >IED1</IEDName>
39+
</GSEControl>
40+
<GSEControl name="gseControl2" datSet="equalDataSet" confRev="1" >
41+
<IEDName >IED1</IEDName>
42+
<IEDName apRef="AP1" ldInst="ldInst" prefix="" lnClass="LLN0" lnInst="" >IED2</IEDName>
43+
<IEDName apRef="AP1" ldInst="ldInst" prefix="CB" lnClass="CSWI" lnInst="1" >IED3</IEDName>
44+
<IEDName >IED1</IEDName>
45+
</GSEControl>
46+
<GSEControl name="gseControl5" datSet="diffDataSet" />
47+
<GSEControl name="gseControl3" datSet="invalidDataSet" />
48+
<GSEControl name="gseControl4" datSet="invalidReference" />
49+
</LN0>
50+
</LDevice>
51+
</Server>
52+
</AccessPoint>
53+
</IED>
54+
</SCL>`,
55+
"application/xml"
56+
);
57+
58+
const baseGSEControl = scl.querySelector(`*[datSet="baseDataSet"]`)!;
59+
const equalGSEControl = scl.querySelector('*[datSet="equalDataSet"]')!;
60+
const diffGSEControl = scl.querySelector('*[datSet="diffDataSet"]')!;
61+
const invalidDataSet = scl.querySelector('*[datSet="invalidDataSet"]')!;
62+
const invalidReference = scl.querySelector('*[datSet="invalidReference"]')!;
63+
64+
describe("Description for SCL schema type tControlWithIEDName", () => {
65+
it("returns undefined when referenced DataSet is undefined", () =>
66+
expect(describeControlWithIEDName(invalidDataSet)).to.be.undefined);
67+
68+
it("returns undefined with missing referenced DataSet", () =>
69+
expect(describeControlWithIEDName(invalidReference)).to.be.undefined);
70+
71+
it("returns same description with semantically equal ControlWithIEDName's", () =>
72+
expect(JSON.stringify(describeControlWithIEDName(baseGSEControl))).to.equal(
73+
JSON.stringify(describeControlWithIEDName(equalGSEControl))
74+
));
75+
76+
it("returns different description with unequal ControlWithIEDName elements", () =>
77+
expect(
78+
JSON.stringify(describeControlWithIEDName(baseGSEControl))
79+
).to.not.equal(JSON.stringify(describeControlWithIEDName(diffGSEControl))));
80+
});

describe/ControlWithIEDName.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { ControlDescription, describeControl } from "./Control.js";
2+
3+
function compareIEDNameDescription(a: IEDName, b: IEDName): number {
4+
const stringifiedA = JSON.stringify(a);
5+
const stringifiedB = JSON.stringify(b);
6+
7+
if (stringifiedA < stringifiedB) return -1;
8+
if (stringifiedA > stringifiedB) return 1;
9+
return 0;
10+
}
11+
12+
type IEDName = {
13+
/** IEDName attribute apRef*/
14+
apRef?: string;
15+
/** IEDName attribute ldInst*/
16+
ldInst?: string;
17+
/** IEDName attribute prefix*/
18+
prefix?: string;
19+
/** IEDName attribute lnClass*/
20+
lnClass?: string;
21+
/** IEDName attribute lnInst*/
22+
lnInst?: string;
23+
/** IEDName child text content */
24+
val?: string;
25+
};
26+
27+
function describeIEDName(element: Element): IEDName {
28+
const iedName: IEDName = {};
29+
30+
const [apRef, ldInst, prefix, lnClass, lnInst] = [
31+
"apRef",
32+
"ldInst",
33+
"prefix",
34+
"lnClass",
35+
"lnInst",
36+
].map((attr) => element.getAttribute(attr));
37+
38+
const val = element.textContent;
39+
40+
if (apRef) iedName.apRef = apRef;
41+
if (ldInst) iedName.ldInst = ldInst;
42+
if (prefix) iedName.prefix = prefix;
43+
if (lnClass) iedName.lnClass = lnClass;
44+
if (lnInst) iedName.lnInst = lnInst;
45+
if (val) iedName.val = val;
46+
47+
return iedName;
48+
}
49+
50+
export interface ControlWithIEDNameDescription extends ControlDescription {
51+
/** ControlWithIEDName children IEDName */
52+
iedNames: IEDName[];
53+
/** ControlWithIEDName attribute confRev defaulted to 0 */
54+
confRev?: number;
55+
}
56+
57+
export function describeControlWithIEDName(
58+
element: Element
59+
): ControlWithIEDNameDescription | undefined {
60+
const controlDescription = describeControl(element);
61+
if (!controlDescription) return;
62+
63+
const controlWithIEDNameDescription: ControlWithIEDNameDescription = {
64+
...controlDescription,
65+
iedNames: [],
66+
};
67+
68+
controlWithIEDNameDescription.iedNames.push(
69+
...Array.from(element.children)
70+
.filter((child) => child.tagName === "IEDName")
71+
.map((iedName) => describeIEDName(iedName))
72+
.sort(compareIEDNameDescription)
73+
);
74+
75+
const confRev = element.getAttribute("confRev");
76+
if (confRev && !isNaN(parseInt(confRev, 10)))
77+
controlWithIEDNameDescription.confRev = parseInt(confRev, 10);
78+
79+
return controlWithIEDNameDescription;
80+
}

describe/GSEControl.spec.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { expect } from "chai";
2+
3+
import { describeGSEControl } from "./GSEControl.js";
4+
5+
const scl = new DOMParser().parseFromString(
6+
`<SCL xmlns="http://www.iec.ch/61850/2003/SCL" >
7+
<IED name="IED1">
8+
<AccessPoint name="AP1">
9+
<Server>
10+
<LDevice inst="lDevice">
11+
<LN0 lnClass="LLN0" inst="" lnType="LLN0">
12+
<DataSet name="baseDataSet" >
13+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
14+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
15+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" daName="stVal" fc="ST" />
16+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" fc="ST" />
17+
</DataSet>
18+
<DataSet name="equalDataSet" >
19+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
20+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
21+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
22+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
23+
</DataSet>
24+
<DataSet name="diffDataSet" >
25+
<Private type="private" />
26+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
27+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="XCBR" lnInst="1" doName="Pos" daName="q" fc="ST" />
28+
<FCDA iedName="IED1" ldInst="lDevice" prefix="" lnClass="LLN0" lnInst="" doName="Beh" daName="stVal" fc="ST" />
29+
<FCDA iedName="IED1" ldInst="lDevice" lnClass="LLN0" doName="Beh" fc="ST" />
30+
</DataSet>
31+
<DataSet name="invalidDataSet" >
32+
<FCDA ldInst="lDevice" prefix="" lnClass="XCBR" lnInst="1" doName="Pos" daName="stVal" fc="ST" />
33+
</DataSet>
34+
<GSEControl name="gseControl1" datSet="baseDataSet" confRev="1" appID="appID" fixedOffs="false" securityEnable="Signature" >
35+
<IEDName apRef="AP1" ldInst="ldInst" prefix="CB" lnClass="CSWI" lnInst="1">IED3</IEDName>
36+
<IEDName >IED1</IEDName>
37+
<IEDName apRef="AP1" ldInst="ldInst" lnClass="LLN0">IED2</IEDName>
38+
<IEDName >IED1</IEDName>
39+
<Protocol mustUnderstand="true">R-GOOSE</Protocol>
40+
</GSEControl>
41+
<GSEControl name="gseControl2" datSet="equalDataSet" confRev="1" appID="appID" securityEnable="Signature" >
42+
<IEDName >IED1</IEDName>
43+
<IEDName apRef="AP1" ldInst="ldInst" prefix="" lnClass="LLN0" lnInst="" >IED2</IEDName>
44+
<IEDName apRef="AP1" ldInst="ldInst" prefix="CB" lnClass="CSWI" lnInst="1" >IED3</IEDName>
45+
<IEDName >IED1</IEDName>
46+
<Protocol mustUnderstand="true">R-GOOSE</Protocol>
47+
</GSEControl>
48+
<GSEControl name="gseControl5" datSet="diffDataSet" type="GSSE" fixedOffs="true" />
49+
<GSEControl name="gseControl3" datSet="invalidDataSet" />
50+
<GSEControl name="gseControl4" datSet="invalidReference" />
51+
</LN0>
52+
</LDevice>
53+
</Server>
54+
</AccessPoint>
55+
</IED>
56+
</SCL>`,
57+
"application/xml"
58+
);
59+
60+
const baseGSEControl = scl.querySelector(`*[datSet="baseDataSet"]`)!;
61+
const equalGSEControl = scl.querySelector('*[datSet="equalDataSet"]')!;
62+
const diffGSEControl = scl.querySelector('*[datSet="diffDataSet"]')!;
63+
const invalidDataSet = scl.querySelector('*[datSet="invalidDataSet"]')!;
64+
const invalidReference = scl.querySelector('*[datSet="invalidReference"]')!;
65+
66+
describe("Description for SCL schema type tControlWithIEDName", () => {
67+
it("returns undefined when referenced DataSet is undefined", () =>
68+
expect(describeGSEControl(invalidDataSet)).to.be.undefined);
69+
70+
it("returns undefined with missing referenced DataSet", () =>
71+
expect(describeGSEControl(invalidReference)).to.be.undefined);
72+
73+
it("returns same description with semantically equal GSEControl's", () =>
74+
expect(JSON.stringify(describeGSEControl(baseGSEControl))).to.equal(
75+
JSON.stringify(describeGSEControl(equalGSEControl))
76+
));
77+
78+
it("returns different description with unequal GSEControl elements", () =>
79+
expect(JSON.stringify(describeGSEControl(baseGSEControl))).to.not.equal(
80+
JSON.stringify(describeGSEControl(diffGSEControl))
81+
));
82+
});

describe/GSEControl.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {
2+
ControlWithIEDNameDescription,
3+
describeControlWithIEDName,
4+
} from "./ControlWithIEDName.js";
5+
6+
export interface GSEControlDescription extends ControlWithIEDNameDescription {
7+
/** GSEControl attribute type defaulted to "GOOSE" */
8+
type: "GOOSE" | "GSSE";
9+
/** GSEControl attribute appId */
10+
appID: string;
11+
/** GSEControl attribute fixedOffs defaulted to false */
12+
fixedOffs: boolean;
13+
/** GSEControl attribute securityEnable defaulted to "None" */
14+
securityEnable: "None" | "Signature" | "SignatureAndEncryption";
15+
/**GSEControl child Protocol*/
16+
protocol?: { mustUnderstand: true; val: "R-GOOSE" };
17+
}
18+
19+
export function describeGSEControl(
20+
element: Element
21+
): GSEControlDescription | undefined {
22+
const controlWithTriggerOptDesc = describeControlWithIEDName(element);
23+
if (!controlWithTriggerOptDesc) return;
24+
25+
const gseControlDescription: GSEControlDescription = {
26+
...controlWithTriggerOptDesc,
27+
type: element.getAttribute("type") === "GSSE" ? "GSSE" : "GOOSE",
28+
appID: element.getAttribute("appID") ?? "",
29+
fixedOffs: element.getAttribute("fixedOffs") === "true" ? true : false,
30+
securityEnable: element.getAttribute("securityEnable")
31+
? (element.getAttribute("securityEnable") as
32+
| "Signature"
33+
| "SignatureAndEncryption")
34+
: "None",
35+
};
36+
37+
const protocol = Array.from(element.children).find(
38+
(child) => child.tagName === "Protocol"
39+
);
40+
if (protocol)
41+
gseControlDescription.protocol = { mustUnderstand: true, val: "R-GOOSE" };
42+
43+
return gseControlDescription;
44+
}

0 commit comments

Comments
 (0)